Skip to content

Commit

Permalink
Add initial implementation of the zpages for Otel Trace (#894)
Browse files Browse the repository at this point in the history
* Add initial implementation of the zpages for Otel Trace

Signed-off-by: Bogdan Drutu <bogdandrutu@gmail.com>

* Address review comments

Signed-off-by: Bogdan Drutu <bogdandrutu@gmail.com>

* Remove copyright header for new files

Signed-off-by: Bogdan Drutu <bogdandrutu@gmail.com>

* Set go 1.15 in go.mod

Signed-off-by: Bogdan Drutu <bogdandrutu@gmail.com>
  • Loading branch information
bogdandrutu committed Jul 21, 2021
1 parent d586bc7 commit 376f3e8
Show file tree
Hide file tree
Showing 18 changed files with 1,464 additions and 0 deletions.
10 changes: 10 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -494,3 +494,13 @@ updates:
schedule:
interval: "weekly"
day: "sunday"
-
package-ecosystem: "gomod"
directory: "/zpages"
labels:
- dependencies
- go
- "Skip Changelog"
schedule:
interval: "weekly"
day: "sunday"
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Unreleased]

### Added

- Add the `zpages` span processor. (#894)

### Fixed

- Fix deadlocks and race conditions in `otelsarama.WrapAsyncProducer`.
Expand Down
61 changes: 61 additions & 0 deletions zpages/boundaries.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// 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 zpages

import (
"sort"
"time"
)

const zeroDuration = time.Duration(0)
const maxDuration = time.Duration(1<<63 - 1)

var defaultBoundaries = newBoundaries([]time.Duration{
10 * time.Microsecond,
100 * time.Microsecond,
time.Millisecond,
10 * time.Millisecond,
100 * time.Millisecond,
time.Second,
10 * time.Second,
100 * time.Second,
})

// boundaries represents the interval bounds for the latency based samples.
type boundaries struct {
durations []time.Duration
}

// newBoundaries returns a new boundaries.
func newBoundaries(durations []time.Duration) *boundaries {
sort.Slice(durations, func(i, j int) bool {
return durations[i] < durations[j]
})
return &boundaries{durations: durations}
}

// numBuckets returns the number of buckets needed for these boundaries.
func (lb boundaries) numBuckets() int {
return len(lb.durations) + 1
}

// getBucketIndex returns the appropriate bucket index for a given latency.
func (lb boundaries) getBucketIndex(latency time.Duration) int {
i := 0
for i < len(lb.durations) && latency >= lb.durations[i] {
i++
}
return i
}
43 changes: 43 additions & 0 deletions zpages/boundaries_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// 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 zpages

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
)

var testDurations = []time.Duration{1 * time.Second}

func TestBoundariesNumBuckets(t *testing.T) {
assert.Equal(t, 1, newBoundaries(nil).numBuckets())
assert.Equal(t, 1, newBoundaries([]time.Duration{}).numBuckets())
assert.Equal(t, 2, newBoundaries(testDurations).numBuckets())
assert.Equal(t, 9, defaultBoundaries.numBuckets())
}

func TestBoundariesGetBucketIndex(t *testing.T) {
assert.Equal(t, 0, newBoundaries(testDurations).getBucketIndex(zeroDuration))
assert.Equal(t, 0, newBoundaries(testDurations).getBucketIndex(500*time.Millisecond))
assert.Equal(t, 1, newBoundaries(testDurations).getBucketIndex(1500*time.Millisecond))
assert.Equal(t, 0, newBoundaries(testDurations).getBucketIndex(zeroDuration))

assert.Equal(t, 0, defaultBoundaries.getBucketIndex(zeroDuration))
assert.Equal(t, 3, defaultBoundaries.getBucketIndex(5*time.Millisecond))
assert.Equal(t, 6, defaultBoundaries.getBucketIndex(5*time.Second))
assert.Equal(t, 8, defaultBoundaries.getBucketIndex(maxDuration))
}
74 changes: 74 additions & 0 deletions zpages/bucket.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright The OpenTelemetry Authors
// Copyright 2017, OpenCensus 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 zpages

import (
"time"

sdktrace "go.opentelemetry.io/otel/sdk/trace"
)

const (
// defaultBucketCapacity is the default capacity for every bucket (latency or error based).
defaultBucketCapacity = 10
// samplePeriod is the minimum time between accepting spans in a single bucket.
samplePeriod = time.Second
)

// bucket is a container for a set of spans for latency buckets or errored spans.
type bucket struct {
nextTime time.Time // next time we can accept a span
buffer []sdktrace.ReadOnlySpan // circular buffer of spans
nextIndex int // location next ReadOnlySpan should be placed in buffer
overflow bool // whether the circular buffer has wrapped around
}

// newBucket returns a new bucket with the given capacity.
func newBucket(capacity uint) *bucket {
return &bucket{
buffer: make([]sdktrace.ReadOnlySpan, capacity),
}
}

// add adds a span to the bucket, if nextTime has been reached.
func (b *bucket) add(s sdktrace.ReadOnlySpan) {
if s.EndTime().Before(b.nextTime) {
return
}
if len(b.buffer) == 0 {
return
}
b.nextTime = s.EndTime().Add(samplePeriod)
b.buffer[b.nextIndex] = s
b.nextIndex++
if b.nextIndex == len(b.buffer) {
b.nextIndex = 0
b.overflow = true
}
}

// len returns the number of spans in the bucket.
func (b *bucket) len() int {
if b.overflow {
return len(b.buffer)
}
return b.nextIndex
}

// spans returns the spans in this bucket.
func (b *bucket) spans() []sdktrace.ReadOnlySpan {
return append([]sdktrace.ReadOnlySpan(nil), b.buffer[0:b.len()]...)
}
104 changes: 104 additions & 0 deletions zpages/bucket_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// 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 zpages

import (
"testing"
"time"

"github.com/stretchr/testify/assert"

sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
)

type testSpan struct {
sdktrace.ReadWriteSpan
spanContext trace.SpanContext
name string
startTime time.Time
endTime time.Time
status sdktrace.Status
}

func (ts *testSpan) SpanContext() trace.SpanContext {
return ts.spanContext
}

func (ts *testSpan) Status() sdktrace.Status {
return ts.status
}

func (ts *testSpan) Name() string {
return ts.name
}

func (ts *testSpan) StartTime() time.Time {
return ts.startTime
}

func (ts *testSpan) EndTime() time.Time {
return ts.endTime
}

func TestBucket(t *testing.T) {
bkt := newBucket(defaultBucketCapacity)
assert.Equal(t, 0, bkt.len())

for i := 1; i <= defaultBucketCapacity; i++ {
bkt.add(&testSpan{endTime: time.Unix(int64(i), 0)})
assert.Equal(t, i, bkt.len())
spans := bkt.spans()
assert.Len(t, spans, i)
for j := 0; j < i; j++ {
assert.Equal(t, time.Unix(int64(j+1), 0), spans[j].EndTime())
}
}

for i := defaultBucketCapacity + 1; i <= 2*defaultBucketCapacity; i++ {
bkt.add(&testSpan{endTime: time.Unix(int64(i), 0)})
assert.Equal(t, defaultBucketCapacity, bkt.len())
spans := bkt.spans()
assert.Len(t, spans, defaultBucketCapacity)
// First spans will have newer times, and will replace older timestamps.
for j := 0; j < i-defaultBucketCapacity; j++ {
assert.Equal(t, time.Unix(int64(j+defaultBucketCapacity+1), 0), spans[j].EndTime())
}
for j := i - defaultBucketCapacity; j < defaultBucketCapacity; j++ {
assert.Equal(t, time.Unix(int64(j+1), 0), spans[j].EndTime())
}
}
}

func TestBucketAddSample(t *testing.T) {
bkt := newBucket(defaultBucketCapacity)
assert.Equal(t, 0, bkt.len())

for i := 0; i < 1000; i++ {
bkt.add(&testSpan{endTime: time.Unix(1, int64(i*1000))})
assert.Equal(t, 1, bkt.len())
spans := bkt.spans()
assert.Len(t, spans, 1)
assert.Equal(t, time.Unix(1, 0), spans[0].EndTime())
}
}

func TestBucketZeroCapacity(t *testing.T) {
bkt := newBucket(0)
assert.Equal(t, 0, bkt.len())
bkt.add(&testSpan{endTime: time.Unix(1, 0)})
assert.Equal(t, 0, bkt.len())
assert.Len(t, bkt.spans(), 0)
}
10 changes: 10 additions & 0 deletions zpages/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module go.opentelemetry.io/contrib/zpages

go 1.15

require (
github.com/stretchr/testify v1.7.0
go.opentelemetry.io/otel v1.0.0-RC1
go.opentelemetry.io/otel/sdk v1.0.0-RC1
go.opentelemetry.io/otel/trace v1.0.0-RC1
)
23 changes: 23 additions & 0 deletions zpages/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
go.opentelemetry.io/otel v1.0.0-RC1 h1:4CeoX93DNTWt8awGK9JmNXzF9j7TyOu9upscEdtcdXc=
go.opentelemetry.io/otel v1.0.0-RC1/go.mod h1:x9tRa9HK4hSSq7jf2TKbqFbtt58/TGk0f9XiEYISI1I=
go.opentelemetry.io/otel/oteltest v1.0.0-RC1 h1:G685iP3XiskCwk/z0eIabL55XUl2gk0cljhGk9sB0Yk=
go.opentelemetry.io/otel/oteltest v1.0.0-RC1/go.mod h1:+eoIG0gdEOaPNftuy1YScLr1Gb4mL/9lpDkZ0JjMRq4=
go.opentelemetry.io/otel/sdk v1.0.0-RC1 h1:Sy2VLOOg24bipyC29PhuMXYNJrLsxkie8hyI7kUlG9Q=
go.opentelemetry.io/otel/sdk v1.0.0-RC1/go.mod h1:kj6yPn7Pgt5ByRuwesbaWcRLA+V7BSDg3Hf8xRvsvf8=
go.opentelemetry.io/otel/trace v1.0.0-RC1 h1:jrjqKJZEibFrDz+umEASeU3LvdVyWKlnTh7XEfwrT58=
go.opentelemetry.io/otel/trace v1.0.0-RC1/go.mod h1:86UHmyHWFEtWjfWPSbu0+d0Pf9Q6e1U+3ViBOc+NXAg=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
18 changes: 18 additions & 0 deletions zpages/internal/gen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// 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 internal

// go get https://github.com/mjibson/esc.git
//go:generate esc -pkg internal -o resources.go templates/
Loading

0 comments on commit 376f3e8

Please sign in to comment.