Add very simple tracing mechanism for easier debugging of slow requests.

This commit is contained in:
Filip Grzadkowski 2015-05-26 15:43:54 +02:00
parent 39483ffa90
commit 967b1b95ba
3 changed files with 110 additions and 0 deletions

View File

@ -18,6 +18,7 @@ package etcd
import ( import (
"fmt" "fmt"
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
kubeerr "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" kubeerr "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
@ -28,6 +29,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools" "github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch" "github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
"github.com/golang/glog" "github.com/golang/glog"
@ -144,22 +146,29 @@ func (e *Etcd) List(ctx api.Context, label labels.Selector, field fields.Selecto
// ListPredicate returns a list of all the items matching m. // ListPredicate returns a list of all the items matching m.
func (e *Etcd) ListPredicate(ctx api.Context, m generic.Matcher) (runtime.Object, error) { func (e *Etcd) ListPredicate(ctx api.Context, m generic.Matcher) (runtime.Object, error) {
trace := util.NewTrace("List")
defer trace.LogIfLong(time.Second)
list := e.NewListFunc() list := e.NewListFunc()
if name, ok := m.MatchesSingle(); ok { if name, ok := m.MatchesSingle(); ok {
trace.Step("About to read single object")
key, err := e.KeyFunc(ctx, name) key, err := e.KeyFunc(ctx, name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = e.Helper.ExtractObjToList(key, list) err = e.Helper.ExtractObjToList(key, list)
trace.Step("Object extracted")
if err != nil { if err != nil {
return nil, err return nil, err
} }
} else { } else {
trace.Step("About to list directory")
err := e.Helper.ExtractToList(e.KeyRootFunc(ctx), list) err := e.Helper.ExtractToList(e.KeyRootFunc(ctx), list)
trace.Step("List extracted")
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
defer trace.Step("List filtered")
return generic.FilterList(list, m, generic.DecoratorFunc(e.Decorator)) return generic.FilterList(list, m, generic.DecoratorFunc(e.Decorator))
} }
@ -192,6 +201,8 @@ func (e *Etcd) CreateWithName(ctx api.Context, name string, obj runtime.Object)
// Create inserts a new item according to the unique key from the object. // Create inserts a new item according to the unique key from the object.
func (e *Etcd) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) { func (e *Etcd) Create(ctx api.Context, obj runtime.Object) (runtime.Object, error) {
trace := util.NewTrace("Create")
defer trace.LogIfLong(time.Second)
if err := rest.BeforeCreate(e.CreateStrategy, ctx, obj); err != nil { if err := rest.BeforeCreate(e.CreateStrategy, ctx, obj); err != nil {
return nil, err return nil, err
} }
@ -210,12 +221,14 @@ func (e *Etcd) Create(ctx api.Context, obj runtime.Object) (runtime.Object, erro
return nil, err return nil, err
} }
} }
trace.Step("About to create object")
out := e.NewFunc() out := e.NewFunc()
if err := e.Helper.CreateObj(key, obj, out, ttl); err != nil { if err := e.Helper.CreateObj(key, obj, out, ttl); err != nil {
err = etcderr.InterpretCreateError(err, e.EndpointName, name) err = etcderr.InterpretCreateError(err, e.EndpointName, name)
err = rest.CheckGeneratedNameError(e.CreateStrategy, err, obj) err = rest.CheckGeneratedNameError(e.CreateStrategy, err, obj)
return nil, err return nil, err
} }
trace.Step("Object created")
if e.AfterCreate != nil { if e.AfterCreate != nil {
if err := e.AfterCreate(out); err != nil { if err := e.AfterCreate(out); err != nil {
return nil, err return nil, err
@ -255,6 +268,8 @@ func (e *Etcd) UpdateWithName(ctx api.Context, name string, obj runtime.Object)
// or an error. If the registry allows create-on-update, the create flow will be executed. // or an error. If the registry allows create-on-update, the create flow will be executed.
// A bool is returned along with the object and any errors, to indicate object creation. // A bool is returned along with the object and any errors, to indicate object creation.
func (e *Etcd) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) { func (e *Etcd) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool, error) {
trace := util.NewTrace("Update")
defer trace.LogIfLong(time.Second)
name, err := e.ObjectNameFunc(obj) name, err := e.ObjectNameFunc(obj)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
@ -343,14 +358,18 @@ func (e *Etcd) Update(ctx api.Context, obj runtime.Object) (runtime.Object, bool
// Get retrieves the item from etcd. // Get retrieves the item from etcd.
func (e *Etcd) Get(ctx api.Context, name string) (runtime.Object, error) { func (e *Etcd) Get(ctx api.Context, name string) (runtime.Object, error) {
trace := util.NewTrace("Get")
defer trace.LogIfLong(time.Second)
obj := e.NewFunc() obj := e.NewFunc()
key, err := e.KeyFunc(ctx, name) key, err := e.KeyFunc(ctx, name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
trace.Step("About to read object")
if err := e.Helper.ExtractObj(key, obj, false); err != nil { if err := e.Helper.ExtractObj(key, obj, false); err != nil {
return nil, etcderr.InterpretGetError(err, e.EndpointName, name) return nil, etcderr.InterpretGetError(err, e.EndpointName, name)
} }
trace.Step("Object read")
if e.Decorator != nil { if e.Decorator != nil {
if err := e.Decorator(obj); err != nil { if err := e.Decorator(obj); err != nil {
return nil, err return nil, err
@ -361,12 +380,15 @@ func (e *Etcd) Get(ctx api.Context, name string) (runtime.Object, error) {
// Delete removes the item from etcd. // Delete removes the item from etcd.
func (e *Etcd) Delete(ctx api.Context, name string, options *api.DeleteOptions) (runtime.Object, error) { func (e *Etcd) Delete(ctx api.Context, name string, options *api.DeleteOptions) (runtime.Object, error) {
trace := util.NewTrace("Delete")
defer trace.LogIfLong(time.Second)
key, err := e.KeyFunc(ctx, name) key, err := e.KeyFunc(ctx, name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
obj := e.NewFunc() obj := e.NewFunc()
trace.Step("About to read object")
if err := e.Helper.ExtractObj(key, obj, false); err != nil { if err := e.Helper.ExtractObj(key, obj, false); err != nil {
return nil, etcderr.InterpretDeleteError(err, e.EndpointName, name) return nil, etcderr.InterpretDeleteError(err, e.EndpointName, name)
} }
@ -383,6 +405,7 @@ func (e *Etcd) Delete(ctx api.Context, name string, options *api.DeleteOptions)
return e.finalizeDelete(obj, false) return e.finalizeDelete(obj, false)
} }
if graceful && *options.GracePeriodSeconds != 0 { if graceful && *options.GracePeriodSeconds != 0 {
trace.Step("Graceful deletion")
out := e.NewFunc() out := e.NewFunc()
if err := e.Helper.SetObj(key, obj, out, uint64(*options.GracePeriodSeconds)); err != nil { if err := e.Helper.SetObj(key, obj, out, uint64(*options.GracePeriodSeconds)); err != nil {
return nil, etcderr.InterpretUpdateError(err, e.EndpointName, name) return nil, etcderr.InterpretUpdateError(err, e.EndpointName, name)
@ -392,6 +415,7 @@ func (e *Etcd) Delete(ctx api.Context, name string, options *api.DeleteOptions)
// delete immediately, or no graceful deletion supported // delete immediately, or no graceful deletion supported
out := e.NewFunc() out := e.NewFunc()
trace.Step("About to delete object")
if err := e.Helper.DeleteObj(key, out); err != nil { if err := e.Helper.DeleteObj(key, out); err != nil {
return nil, etcderr.InterpretDeleteError(err, e.EndpointName, name) return nil, etcderr.InterpretDeleteError(err, e.EndpointName, name)
} }

View File

@ -181,6 +181,8 @@ func (h *EtcdHelper) listEtcdNode(key string) ([]*etcd.Node, uint64, error) {
// decodeNodeList walks the tree of each node in the list and decodes into the specified object // decodeNodeList walks the tree of each node in the list and decodes into the specified object
func (h *EtcdHelper) decodeNodeList(nodes []*etcd.Node, slicePtr interface{}) error { func (h *EtcdHelper) decodeNodeList(nodes []*etcd.Node, slicePtr interface{}) error {
trace := util.NewTrace("decodeNodeList " + getTypeName(slicePtr))
defer trace.LogIfLong(500 * time.Millisecond)
v, err := conversion.EnforcePtr(slicePtr) v, err := conversion.EnforcePtr(slicePtr)
if err != nil || v.Kind() != reflect.Slice { if err != nil || v.Kind() != reflect.Slice {
// This should not happen at runtime. // This should not happen at runtime.
@ -188,9 +190,11 @@ func (h *EtcdHelper) decodeNodeList(nodes []*etcd.Node, slicePtr interface{}) er
} }
for _, node := range nodes { for _, node := range nodes {
if node.Dir { if node.Dir {
trace.Step("Decoding dir " + node.Key + " START")
if err := h.decodeNodeList(node.Nodes, slicePtr); err != nil { if err := h.decodeNodeList(node.Nodes, slicePtr); err != nil {
return err return err
} }
trace.Step("Decoding dir " + node.Key + " END")
continue continue
} }
if obj, found := h.getFromCache(node.ModifiedIndex); found { if obj, found := h.getFromCache(node.ModifiedIndex); found {
@ -210,6 +214,7 @@ func (h *EtcdHelper) decodeNodeList(nodes []*etcd.Node, slicePtr interface{}) er
} }
} }
} }
trace.Step(fmt.Sprintf("Decoded %v nodes", len(nodes)))
return nil return nil
} }
@ -222,15 +227,19 @@ type etcdCache interface {
} }
func (h *EtcdHelper) getFromCache(index uint64) (runtime.Object, bool) { func (h *EtcdHelper) getFromCache(index uint64) (runtime.Object, bool) {
trace := util.NewTrace("getFromCache")
defer trace.LogIfLong(200 * time.Microsecond)
startTime := time.Now() startTime := time.Now()
defer func() { defer func() {
cacheGetLatency.Observe(float64(time.Since(startTime) / time.Microsecond)) cacheGetLatency.Observe(float64(time.Since(startTime) / time.Microsecond))
}() }()
obj, found := h.cache.Get(index) obj, found := h.cache.Get(index)
trace.Step("Raw get done")
if found { if found {
// We should not return the object itself to avoid poluting the cache if someone // We should not return the object itself to avoid poluting the cache if someone
// modifies returned values. // modifies returned values.
objCopy, err := conversion.DeepCopy(obj) objCopy, err := conversion.DeepCopy(obj)
trace.Step("Deep copied")
if err != nil { if err != nil {
glog.Errorf("Error during DeepCopy of cached object: %q", err) glog.Errorf("Error during DeepCopy of cached object: %q", err)
return nil, false return nil, false
@ -261,20 +270,25 @@ func (h *EtcdHelper) addToCache(index uint64, obj runtime.Object) {
// ExtractToList works on a *List api object (an object that satisfies the runtime.IsList // ExtractToList works on a *List api object (an object that satisfies the runtime.IsList
// definition) and extracts a go object per etcd node into a slice with the resource version. // definition) and extracts a go object per etcd node into a slice with the resource version.
func (h *EtcdHelper) ExtractToList(key string, listObj runtime.Object) error { func (h *EtcdHelper) ExtractToList(key string, listObj runtime.Object) error {
trace := util.NewTrace("ExtractToList " + getTypeName(listObj))
defer trace.LogIfLong(time.Second)
listPtr, err := runtime.GetItemsPtr(listObj) listPtr, err := runtime.GetItemsPtr(listObj)
if err != nil { if err != nil {
return err return err
} }
key = h.PrefixEtcdKey(key) key = h.PrefixEtcdKey(key)
startTime := time.Now() startTime := time.Now()
trace.Step("About to list etcd node")
nodes, index, err := h.listEtcdNode(key) nodes, index, err := h.listEtcdNode(key)
recordEtcdRequestLatency("list", getTypeName(listPtr), startTime) recordEtcdRequestLatency("list", getTypeName(listPtr), startTime)
trace.Step("Etcd node listed")
if err != nil { if err != nil {
return err return err
} }
if err := h.decodeNodeList(nodes, listPtr); err != nil { if err := h.decodeNodeList(nodes, listPtr); err != nil {
return err return err
} }
trace.Step("Node list decoded")
if h.Versioner != nil { if h.Versioner != nil {
if err := h.Versioner.UpdateList(listObj, index); err != nil { if err := h.Versioner.UpdateList(listObj, index); err != nil {
return err return err
@ -286,14 +300,17 @@ func (h *EtcdHelper) ExtractToList(key string, listObj runtime.Object) error {
// ExtractObjToList unmarshals json found at key and opaques it into a *List api object // ExtractObjToList unmarshals json found at key and opaques it into a *List api object
// (an object that satisfies the runtime.IsList definition). // (an object that satisfies the runtime.IsList definition).
func (h *EtcdHelper) ExtractObjToList(key string, listObj runtime.Object) error { func (h *EtcdHelper) ExtractObjToList(key string, listObj runtime.Object) error {
trace := util.NewTrace("ExtractObjToList " + getTypeName(listObj))
listPtr, err := runtime.GetItemsPtr(listObj) listPtr, err := runtime.GetItemsPtr(listObj)
if err != nil { if err != nil {
return err return err
} }
key = h.PrefixEtcdKey(key) key = h.PrefixEtcdKey(key)
startTime := time.Now() startTime := time.Now()
trace.Step("About to read etcd node")
response, err := h.Client.Get(key, false, false) response, err := h.Client.Get(key, false, false)
recordEtcdRequestLatency("get", getTypeName(listPtr), startTime) recordEtcdRequestLatency("get", getTypeName(listPtr), startTime)
trace.Step("Etcd node read")
if err != nil { if err != nil {
if IsEtcdNotFound(err) { if IsEtcdNotFound(err) {
return nil return nil
@ -307,6 +324,7 @@ func (h *EtcdHelper) ExtractObjToList(key string, listObj runtime.Object) error
if err := h.decodeNodeList(nodes, listPtr); err != nil { if err := h.decodeNodeList(nodes, listPtr); err != nil {
return err return err
} }
trace.Step("Object decoded")
if h.Versioner != nil { if h.Versioner != nil {
if err := h.Versioner.UpdateList(listObj, response.EtcdIndex); err != nil { if err := h.Versioner.UpdateList(listObj, response.EtcdIndex); err != nil {
return err return err

68
pkg/util/trace.go Normal file
View File

@ -0,0 +1,68 @@
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
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 util
import (
"bytes"
"fmt"
"time"
"github.com/golang/glog"
)
type traceStep struct {
stepTime time.Time
msg string
}
type Trace struct {
name string
startTime time.Time
steps []*traceStep
}
func NewTrace(name string) *Trace {
return &Trace{name, time.Now(), make([]*traceStep, 0)}
}
func (t *Trace) Step(msg string) {
t.steps = append(t.steps, &traceStep{time.Now(), msg})
}
func (t *Trace) Log() {
endTime := time.Now()
var buffer bytes.Buffer
buffer.WriteString(fmt.Sprintf("Trace %q (started %v):\n", t.name, t.startTime))
lastStepTime := t.startTime
for _, step := range t.steps {
buffer.WriteString(fmt.Sprintf("[%v] [%v] %v\n", step.stepTime.Sub(t.startTime), step.stepTime.Sub(lastStepTime), step.msg))
lastStepTime = step.stepTime
}
buffer.WriteString(fmt.Sprintf("[%v] [%v] END\n", endTime.Sub(t.startTime), endTime.Sub(lastStepTime)))
glog.Info(buffer.String())
}
func (t *Trace) LogIfLong(threshold time.Duration) {
if time.Since(t.startTime) >= threshold {
t.Log()
}
}
func (t *Trace) TotalTime() time.Duration {
return time.Since(t.startTime)
}