Skip to content

Commit e3604d9

Browse files
authored
Initial commit of the exporter daemon and its Go exporter (#1)
This commit contains an initial prototype for the exporter daemon that can discovered via and endpoint file on the host. The exporter looks for the endpoint file to see the availability of the daemon and exports if it is running. Exporter daemon will allow OpenCensus users to export without having to link a vendor-specific exporter in their final binaries. We are expecting the prototype exporter is going to be implemented in every language and registered by default. Updates census-instrumentation/opencensus-specs#72.
1 parent fd154a3 commit e3604d9

File tree

4 files changed

+351
-0
lines changed

4 files changed

+351
-0
lines changed

cmd/opencensusd/main.go

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright 2018, OpenCensus 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+
15+
// Program opencensusd collects OpenCensus stats and traces
16+
// to export to a configured backend.
17+
package main
18+
19+
import (
20+
"fmt"
21+
"io"
22+
"io/ioutil"
23+
"log"
24+
"net"
25+
"os"
26+
"os/signal"
27+
"path/filepath"
28+
29+
pb "github.com/census-instrumentation/opencensus-proto/gen-go/exporterproto"
30+
"github.com/census-instrumentation/opencensus-service/internal"
31+
"google.golang.org/grpc"
32+
)
33+
34+
func main() {
35+
ls, err := net.Listen("tcp", "127.0.0.1:")
36+
if err != nil {
37+
log.Fatalf("Cannot listen: %v", err)
38+
}
39+
40+
endpointFile := internal.DefaultEndpointFile()
41+
if err := os.MkdirAll(filepath.Dir(endpointFile), 0755); err != nil {
42+
log.Fatalf("Cannot make directory for the endpoint file: %v", err)
43+
}
44+
if err := ioutil.WriteFile(endpointFile, []byte(ls.Addr().String()), 0777); err != nil {
45+
log.Fatalf("Cannot write the endpoint file: %v", err)
46+
}
47+
48+
c := make(chan os.Signal, 1)
49+
signal.Notify(c, os.Interrupt)
50+
go func() {
51+
<-c
52+
os.Remove(endpointFile)
53+
os.Exit(0)
54+
}()
55+
56+
s := grpc.NewServer()
57+
pb.RegisterExportServer(s, &server{})
58+
if err := s.Serve(ls); err != nil {
59+
log.Fatalf("Failed to serve: %v", err)
60+
}
61+
}
62+
63+
type server struct{}
64+
65+
func (s *server) ExportSpan(stream pb.Export_ExportSpanServer) error {
66+
for {
67+
in, err := stream.Recv()
68+
if err == io.EOF {
69+
return nil
70+
}
71+
if err != nil {
72+
return err
73+
}
74+
// TODO(jbd): Implement.
75+
fmt.Println(in)
76+
}
77+
}
78+
79+
func (s *server) ExportMetrics(stream pb.Export_ExportMetricsServer) error {
80+
for {
81+
in, err := stream.Recv()
82+
if err == io.EOF {
83+
return nil
84+
}
85+
if err != nil {
86+
return err
87+
}
88+
// TODO(jbd): Implement.
89+
fmt.Println(in)
90+
}
91+
}
92+
93+
// TODO(jbd): Implement exporting to a backend.

example/main.go

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright 2018, OpenCensus 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+
15+
// Sample contains a program that exports to the OpenCensus service.
16+
package main
17+
18+
import (
19+
"context"
20+
"time"
21+
22+
"github.com/census-instrumentation/opencensus-service/exporter"
23+
"go.opencensus.io/trace"
24+
)
25+
26+
func main() {
27+
trace.RegisterExporter(&exporter.Exporter{})
28+
trace.ApplyConfig(trace.Config{
29+
DefaultSampler: trace.AlwaysSample(),
30+
})
31+
32+
for {
33+
_, span := trace.StartSpan(context.Background(), "foo")
34+
time.Sleep(100 * time.Millisecond)
35+
span.End()
36+
}
37+
}

exporter/exporter.go

+177
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
// Copyright 2018, OpenCensus 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+
15+
// Package exporter contains an OpenCensus service exporter.
16+
package exporter
17+
18+
import (
19+
"context"
20+
"fmt"
21+
"io"
22+
"io/ioutil"
23+
"log"
24+
"os"
25+
"sync"
26+
"time"
27+
28+
"go.opencensus.io/trace"
29+
"google.golang.org/grpc"
30+
31+
"github.com/census-instrumentation/opencensus-proto/gen-go/exporterproto"
32+
"github.com/census-instrumentation/opencensus-proto/gen-go/traceproto"
33+
"github.com/census-instrumentation/opencensus-service/internal"
34+
"github.com/golang/protobuf/ptypes/timestamp"
35+
)
36+
37+
var debug bool
38+
39+
func init() {
40+
_, debug = os.LookupEnv("OPENCENSUS_DEBUG")
41+
}
42+
43+
type Exporter struct {
44+
OnError func(err error)
45+
46+
initOnce sync.Once
47+
48+
clientMu sync.Mutex
49+
clientEndpoint string
50+
spanClient exporterproto.Export_ExportSpanClient
51+
}
52+
53+
func (e *Exporter) init() {
54+
go func() {
55+
for {
56+
e.lookup()
57+
}
58+
}()
59+
}
60+
61+
func (e *Exporter) lookup() {
62+
defer func() {
63+
debugPrintf("Sleeping for a minute...")
64+
time.Sleep(60 * time.Second)
65+
}()
66+
67+
debugPrintf("Looking for the endpoint file")
68+
file := internal.DefaultEndpointFile()
69+
ep, err := ioutil.ReadFile(file)
70+
if os.IsNotExist(err) {
71+
e.deleteClient()
72+
debugPrintf("Endpoint file doesn't exist; disabling exporting")
73+
return
74+
}
75+
if err != nil {
76+
e.onError(err)
77+
return
78+
}
79+
80+
e.clientMu.Lock()
81+
oldendpoint := e.clientEndpoint
82+
e.clientMu.Unlock()
83+
84+
endpoint := string(ep)
85+
if oldendpoint == endpoint {
86+
debugPrintf("Endpoint hasn't changed, doing nothing")
87+
return
88+
}
89+
90+
debugPrintf("Dialing %v...", endpoint)
91+
conn, err := grpc.Dial(endpoint, grpc.WithInsecure())
92+
if err != nil {
93+
e.onError(err)
94+
return
95+
}
96+
97+
e.clientMu.Lock()
98+
defer e.clientMu.Unlock()
99+
100+
client := exporterproto.NewExportClient(conn)
101+
e.spanClient, err = client.ExportSpan(context.Background())
102+
if err != nil {
103+
e.onError(err)
104+
return
105+
}
106+
}
107+
108+
func (e *Exporter) onError(err error) {
109+
if e.OnError != nil {
110+
e.OnError(err)
111+
return
112+
}
113+
log.Printf("Exporter fail: %v", err)
114+
}
115+
116+
// ExportSpan exports span data to the exporter daemon.
117+
func (e *Exporter) ExportSpan(sd *trace.SpanData) {
118+
e.initOnce.Do(e.init)
119+
120+
e.clientMu.Lock()
121+
spanClient := e.spanClient
122+
e.clientMu.Unlock()
123+
124+
if spanClient == nil {
125+
return
126+
}
127+
128+
debugPrintf("Exporting span [%v]", sd.SpanContext)
129+
s := &traceproto.Span{
130+
TraceId: sd.SpanContext.TraceID[:],
131+
SpanId: sd.SpanContext.SpanID[:],
132+
ParentSpanId: sd.ParentSpanID[:],
133+
Name: &traceproto.TruncatableString{
134+
Value: sd.Name,
135+
},
136+
StartTime: &timestamp.Timestamp{
137+
Seconds: sd.StartTime.Unix(),
138+
Nanos: int32(sd.StartTime.Nanosecond()),
139+
},
140+
EndTime: &timestamp.Timestamp{
141+
Seconds: sd.EndTime.Unix(),
142+
Nanos: int32(sd.EndTime.Nanosecond()),
143+
},
144+
// TODO(jbd): Add attributes and others.
145+
}
146+
147+
if err := spanClient.Send(&exporterproto.ExportSpanRequest{
148+
Spans: []*traceproto.Span{s},
149+
}); err != nil {
150+
if err == io.EOF {
151+
debugPrintf("Connection is unavailable; will try to reconnect in a minute")
152+
e.deleteClient()
153+
} else {
154+
e.onError(err)
155+
}
156+
}
157+
}
158+
159+
func (e *Exporter) deleteClient() {
160+
e.clientMu.Lock()
161+
e.spanClient = nil
162+
e.clientEndpoint = ""
163+
e.clientMu.Unlock()
164+
}
165+
166+
func debugPrintf(msg string, arg ...interface{}) {
167+
if debug {
168+
if len(arg) > 0 {
169+
fmt.Printf(msg, arg)
170+
} else {
171+
fmt.Printf(msg)
172+
}
173+
fmt.Println()
174+
}
175+
}
176+
177+
// TODO(jbd): Add stats/metrics exporter.

internal/internal.go

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright 2018, OpenCensus 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+
15+
package internal
16+
17+
import (
18+
"os"
19+
"os/user"
20+
"path/filepath"
21+
"runtime"
22+
)
23+
24+
// DefaultEndpointFile is the location where the
25+
// endpoint file is at on the current platform.
26+
func DefaultEndpointFile() string {
27+
const f = "opencensus.endpoint"
28+
if runtime.GOOS == "windows" {
29+
return filepath.Join(os.Getenv("APPDATA"), "opencensus", f)
30+
}
31+
return filepath.Join(guessUnixHomeDir(), ".config", f)
32+
}
33+
34+
func guessUnixHomeDir() string {
35+
// Prefer $HOME over user.Current due to glibc bug: golang.org/issue/13470
36+
if v := os.Getenv("HOME"); v != "" {
37+
return v
38+
}
39+
// Else, fall back to user.Current:
40+
if u, err := user.Current(); err == nil {
41+
return u.HomeDir
42+
}
43+
return ""
44+
}

0 commit comments

Comments
 (0)