diff --git a/pkg/probe/probe.go b/pkg/probe/probe.go new file mode 100644 index 000000000..c686fc23d --- /dev/null +++ b/pkg/probe/probe.go @@ -0,0 +1,157 @@ +/* + * Minimalist Object Storage, (C) 2015 Minio, Inc. + * + * 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 probe implements a simple mechanism to trace and return errors in large programs. +package probe + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "runtime" + "strconv" + "strings" + "sync" + + "github.com/dustin/go-humanize" +) + +// GetSysInfo returns useful system statistics. +func GetSysInfo() map[string]string { + host, err := os.Hostname() + if err != nil { + host = "" + } + memstats := &runtime.MemStats{} + runtime.ReadMemStats(memstats) + return map[string]string{ + "host.name": host, + "host.os": runtime.GOOS, + "host.arch": runtime.GOARCH, + "host.lang": runtime.Version(), + "host.cpus": strconv.Itoa(runtime.NumCPU()), + "mem.used": humanize.Bytes(memstats.Alloc), + "mem.total": humanize.Bytes(memstats.Sys), + "mem.heap.used": humanize.Bytes(memstats.HeapAlloc), + "mem.heap.total": humanize.Bytes(memstats.HeapSys), + } +} + +type tracePoint struct { + Line int `json:"Line"` + Filename string `json:"File"` + Function string `json:"Func"` + Env map[string][]string `json:"Env"` +} + +// Error implements tracing error functionality. +type Error struct { + lock sync.Mutex + e error + sysInfo map[string]string + tracePoints []tracePoint +} + +// New function instantiates an error probe for tracing. Original errors.error (golang's error +// interface) is injected in only once during this New call. Rest of the time, you +// trace the return path with Probe.Trace and finally handle reporting or quitting +// at the top level. +func New(e error) *Error { + return &Error{sync.Mutex{}, e, GetSysInfo(), []tracePoint{}} +} + +// Trace records the point at which it is invoked. Stack traces are important for +// debugging purposes. +func (e *Error) Trace(fields ...string) *Error { + e.lock.Lock() + defer e.lock.Unlock() + + pc, file, line, _ := runtime.Caller(1) + function := runtime.FuncForPC(pc).Name() + _, function = filepath.Split(function) + file = "..." + strings.TrimPrefix(file, os.Getenv("GOPATH")) // trim gopathSource from file + tp := tracePoint{} + if len(fields) > 0 { + tp = tracePoint{Line: line, Filename: file, Function: function, Env: map[string][]string{"Tags": fields}} + } else { + tp = tracePoint{Line: line, Filename: file, Function: function} + } + e.tracePoints = append(e.tracePoints, tp) + return e +} + +// Untrace erases last trace entry. +func (e *Error) Untrace() { + e.lock.Lock() + defer e.lock.Unlock() + + l := len(e.tracePoints) + if l == 0 { + return + } + // topTP := e.tracePoints[l-1] + e.tracePoints = e.tracePoints[:l-1] +} + +// Error returns error string. +func (e *Error) Error() string { + return e.String() +} + +// String returns error message. +func (e *Error) String() string { + trace := e.e.Error() + "\n" + for i, tp := range e.tracePoints { + if len(tp.Env) > 0 { + trace += fmt.Sprintf(" (%d) %s:%d %s(..) Tags: [%s]\n", i, tp.Filename, tp.Line, tp.Function, strings.Join(tp.Env["Tags"], ", ")) + } else { + trace += fmt.Sprintf(" (%d) %s:%d %s(..)\n", i, tp.Filename, tp.Line, tp.Function) + } + } + + trace += " Host:" + e.sysInfo["host.name"] + " | " + trace += "OS:" + e.sysInfo["host.os"] + " | " + trace += "Arch:" + e.sysInfo["host.arch"] + " | " + trace += "Lang:" + e.sysInfo["host.lang"] + " | " + trace += "Mem:" + e.sysInfo["mem.used"] + "/" + e.sysInfo["mem.total"] + " | " + trace += "Heap:" + e.sysInfo["mem.heap.used"] + "/" + e.sysInfo["mem.heap.total"] + + return trace +} + +// JSON returns JSON formated error trace. +func (e *Error) JSON() string { + anonError := struct { + SysInfo map[string]string + TracePoints []tracePoint + }{ + e.sysInfo, + e.tracePoints, + } + + // jBytes, err := json.Marshal(anonError) + jBytes, err := json.MarshalIndent(anonError, "", "\t") + if err != nil { + return "" + } + return string(jBytes) +} + +// ToError returns original emnedded error. +func (e *Error) ToError() error { + return e.e +} diff --git a/pkg/probe/probe_test.go b/pkg/probe/probe_test.go new file mode 100644 index 000000000..b630e847a --- /dev/null +++ b/pkg/probe/probe_test.go @@ -0,0 +1,40 @@ +/* + * Minio Cloud Storage, (C) 2015 Minio, Inc. + * + * 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 probe + +import ( + "fmt" + "os" + "testing" +) + +func testDummy() *Error { + _, e := os.Stat("this-file-cannot-exit-234234232423423") + es := New(e) + es.Trace("this is hell", "asdf") + return es +} + +func TestProbe(t *testing.T) { + es := testDummy() + if es == nil { + t.Fail() + } + es.Trace() + fmt.Println(es) + // fmt.Println(es.JSON()) + // fmt.Println(es.ToError()) +}