Package Profiling

- new package to use/consume CPU / MEM pprof root package
- create a file on same location as runable binary to store profile

Package HTTPClient
- add message function called on each Dial/DialContext call function
- function message can be nil

Package Server
- add generic function for recover message / catching
- implement this recovring function into runner StartStop & Ticker

Package Logger:
- implement generic recovering function into hook
- fix bug if instance is an invalid instance of fields or entry
This commit is contained in:
Nicolas JUHEL
2024-02-28 12:58:31 +01:00
parent 9b350cb499
commit a9a4d1e7c2
19 changed files with 345 additions and 36 deletions

View File

@@ -146,7 +146,7 @@ func (o *componentHttpClient) _runCli() error {
if cfg, err = o._getConfig(); err != nil {
return prt.Error(err)
} else if dns = cfg.New(o.x.GetContext(), o.getRootCA); dns == nil {
} else if dns = cfg.New(o.x.GetContext(), o.getRootCA, o.getMessage()); dns == nil {
return prt.Error(fmt.Errorf("cannot create DNS Mapper"))
}

View File

@@ -43,15 +43,17 @@ type ComponentHTTPClient interface {
Config() htcdns.Config
SetDefault()
SetAsDefaultHTTPClient(flag bool)
SetFuncMessage(f htcdns.FuncMessage)
}
func New(ctx libctx.FuncContext, defCARoot libtls.FctRootCA, isDeftHTTPClient bool) ComponentHTTPClient {
func New(ctx libctx.FuncContext, defCARoot libtls.FctRootCA, isDeftHTTPClient bool, msg htcdns.FuncMessage) ComponentHTTPClient {
c := &componentHttpClient{
x: libctx.NewConfig[uint8](ctx),
c: new(atomic.Value),
d: new(atomic.Value),
f: new(atomic.Value),
s: new(atomic.Bool),
m: new(atomic.Value),
}
if defCARoot == nil {
@@ -60,8 +62,13 @@ func New(ctx libctx.FuncContext, defCARoot libtls.FctRootCA, isDeftHTTPClient bo
}
}
if msg == nil {
msg = func(msg string) {}
}
c.f.Store(defCARoot)
c.s.Store(isDeftHTTPClient)
c.m.Store(msg)
return c
}
@@ -70,8 +77,8 @@ func Register(cfg libcfg.Config, key string, cpt ComponentHTTPClient) {
cfg.ComponentSet(key, cpt)
}
func RegisterNew(ctx libctx.FuncContext, cfg libcfg.Config, key string, defCARoot libtls.FctRootCA, isDeftHTTPClient bool) {
cfg.ComponentSet(key, New(ctx, defCARoot, isDeftHTTPClient))
func RegisterNew(ctx libctx.FuncContext, cfg libcfg.Config, key string, defCARoot libtls.FctRootCA, isDeftHTTPClient bool, msg htcdns.FuncMessage) {
cfg.ComponentSet(key, New(ctx, defCARoot, isDeftHTTPClient, msg))
}
func Load(getCpt cfgtps.FuncCptGet, key string) ComponentHTTPClient {

View File

@@ -41,6 +41,7 @@ type componentHttpClient struct {
d *atomic.Value // htcdns.DNSMapper
f *atomic.Value // FuncDefaultCARoot
s *atomic.Bool // is Default at start / update
m *atomic.Value // htcdns.FctMessage
}
func (o *componentHttpClient) getRootCA() []string {
@@ -55,6 +56,22 @@ func (o *componentHttpClient) getRootCA() []string {
}
}
func (o *componentHttpClient) getMessage() htcdns.FuncMessage {
if i := o.m.Load(); i == nil {
return nil
} else if v, k := i.(htcdns.FuncMessage); !k {
return nil
} else {
return v
}
}
func (o *componentHttpClient) SetFuncMessage(f htcdns.FuncMessage) {
if f != nil {
o.m.Store(f)
}
}
func (o *componentHttpClient) getDNSMapper() htcdns.DNSMapper {
if i := o.d.Load(); i == nil {
return nil

View File

@@ -73,7 +73,7 @@ func initDNSMapper() htcdns.DNSMapper {
TimeoutIdleConn: libdur.ParseDuration(30 * time.Second),
TimeoutResponseHeader: 0,
},
}, nil)
}, nil, nil)
}
func DefaultDNSMapper() htcdns.DNSMapper {

View File

@@ -121,6 +121,6 @@ func (o Config) Validate() liberr.Error {
return e
}
func (o Config) New(ctx context.Context, fct libtls.FctRootCA) DNSMapper {
return New(ctx, &o, fct)
func (o Config) New(ctx context.Context, fct libtls.FctRootCA, msg FuncMessage) DNSMapper {
return New(ctx, &o, fct, msg)
}

View File

@@ -27,6 +27,7 @@ package dns_mapper_test
import (
"fmt"
"os"
"time"
libdur "github.com/nabbar/golib/duration"
@@ -88,7 +89,9 @@ func init() {
addDns("*.*.test.example.com", numIdx("*"), addIdx("127.0.0."), numIdx("8"))
idx++
dns = htcdns.New(ctx, &opt, nil)
dns = htcdns.New(ctx, &opt, nil, func(msg string) {
_, _ = fmt.Fprintln(os.Stdout, msg)
})
}
func addIdx(src string) string {

View File

@@ -38,6 +38,8 @@ import (
libdur "github.com/nabbar/golib/duration"
)
type FuncMessage func(msg string)
type DNSMapper interface {
Add(from, to string)
Get(from string) string
@@ -58,7 +60,7 @@ type DNSMapper interface {
TimeCleaner(ctx context.Context, dur time.Duration)
}
func New(ctx context.Context, cfg *Config, fct libtls.FctRootCA) DNSMapper {
func New(ctx context.Context, cfg *Config, fct libtls.FctRootCA, msg FuncMessage) DNSMapper {
if cfg == nil {
cfg = &Config{
DNSMapper: make(map[string]string),
@@ -76,12 +78,17 @@ func New(ctx context.Context, cfg *Config, fct libtls.FctRootCA) DNSMapper {
}
}
if msg == nil {
msg = func(msg string) {}
}
d := &dmp{
d: new(sync.Map),
z: new(sync.Map),
c: new(atomic.Value),
t: new(atomic.Value),
f: fct,
i: msg,
}
for edp, adr := range cfg.DNSMapper {

View File

@@ -41,6 +41,7 @@ type dmp struct {
c *atomic.Value // *Config
t *atomic.Value // *http transport
f libtls.FctRootCA
i func(msg string)
}
func (o *dmp) config() *Config {
@@ -99,3 +100,9 @@ func (o *dmp) TimeCleaner(ctx context.Context, dur time.Duration) {
}
}()
}
func (o *dmp) Message(msg string) {
if o.i != nil {
o.i(msg)
}
}

View File

@@ -29,6 +29,7 @@ package dns_mapper
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"net/url"
@@ -60,6 +61,7 @@ func (o *dmp) DialContext(ctx context.Context, network, address string) (net.Con
if dst, e = o.SearchWithCache(address); e != nil {
return nil, e
} else {
o.Message(fmt.Sprintf("Dialing '%s %s' => '%s %s'", network, address, network, dst))
o.CacheSet(address, dst)
return d.DialContext(ctx, network, dst)
}

View File

@@ -26,8 +26,10 @@
package httpcli_test
import (
"fmt"
"io"
"net/http"
"os"
"time"
libdur "github.com/nabbar/golib/duration"
@@ -59,7 +61,9 @@ var _ = Describe("HttpCli", func() {
Transport: htcdns.TransportConfig{},
}
dns = htcdns.New(ctx, &opt, nil)
dns = htcdns.New(ctx, &opt, nil, func(msg string) {
_, _ = fmt.Fprintln(os.Stdout, msg)
})
cli = dns.DefaultClient()
Expect(cli).ToNot(BeNil())

View File

@@ -33,23 +33,45 @@ import (
// FieldAdd allow to add one couple key/val as type string/interface into the custom field of the entry.
func (e *entry) FieldAdd(key string, val interface{}) Entry {
if e == nil {
return nil
} else if e.Fields == nil {
return nil
}
e.Fields.Add(key, val)
return e
}
// FieldMerge allow to merge a Field pointer into the custom field of the entry.
func (e *entry) FieldMerge(fields logfld.Fields) Entry {
if e == nil {
return nil
} else if e.Fields == nil {
return nil
}
e.Fields.Merge(fields)
return e
}
// FieldSet allow to change the custom field of the entry with the given Fields in parameter.
func (e *entry) FieldSet(fields logfld.Fields) Entry {
if e == nil {
return nil
}
e.Fields = fields
return e
}
func (e *entry) FieldClean(keys ...string) Entry {
if e == nil {
return nil
} else if e.Fields == nil {
return nil
}
for _, k := range keys {
e.Fields.Delete(k)
}

View File

@@ -76,42 +76,72 @@ type entry struct {
}
func (e *entry) SetEntryContext(etime time.Time, stack uint64, caller, file string, line uint64, msg string) Entry {
if e == nil {
return nil
}
e.Time = etime
e.Stack = stack
e.Caller = caller
e.File = file
e.Line = line
e.Message = msg
return e
}
func (e *entry) SetMessageOnly(flag bool) Entry {
if e == nil {
return nil
}
e.clean = flag
return e
}
func (e *entry) SetLevel(lvl loglvl.Level) Entry {
if e == nil {
return nil
}
e.Level = lvl
return e
}
func (e *entry) SetLogger(fct func() *logrus.Logger) Entry {
if e == nil {
return nil
}
e.log = fct
return e
}
// SetGinContext allow to register a gin context pointer to register the errors of the current entry intro gin Context Error Slice.
func (e *entry) SetGinContext(ctx *ginsdk.Context) Entry {
if e == nil {
return nil
}
e.gin = ctx
return e
}
func (e *entry) DataSet(data interface{}) Entry {
if e == nil {
return nil
}
e.Data = data
return e
}
func (e *entry) Check(lvlNoErr loglvl.Level) bool {
if e == nil {
return false
}
var found = false
if len(e.Error) > 0 {
for _, er := range e.Error {
@@ -135,6 +165,8 @@ func (e *entry) Check(lvlNoErr loglvl.Level) bool {
func (e *entry) Log() {
if e == nil {
return
} else if e.log == nil {
return
} else if e.Fields == nil {
return
} else if e.Fields.Err() != nil {

View File

@@ -40,12 +40,26 @@ type fldModel struct {
}
func (o *fldModel) Add(key string, val interface{}) Fields {
if o == nil {
return nil
} else if o.Config == nil {
return nil
}
o.Store(key, val)
return o
}
func (o *fldModel) Logrus() logrus.Fields {
var res = make(logrus.Fields, 0)
if o == nil {
return res
} else if o.Config == nil {
return res
}
o.Walk(func(key string, val interface{}) bool {
res[key] = val
return true
@@ -54,10 +68,17 @@ func (o *fldModel) Logrus() logrus.Fields {
}
func (o *fldModel) Map(fct func(key string, val interface{}) interface{}) Fields {
if o == nil {
return nil
} else if o.Config == nil {
return nil
}
o.Walk(func(key string, val interface{}) bool {
o.Store(key, fct(key, val))
return true
})
return o
}

View File

@@ -37,6 +37,7 @@ import (
"time"
libiot "github.com/nabbar/golib/ioutils"
libsrv "github.com/nabbar/golib/server"
)
const sizeBuffer = 32 * 1024
@@ -81,9 +82,7 @@ func (o *hkf) writeBuffer(buf *bytes.Buffer) error {
}
defer func() {
if rec := recover(); rec != nil {
_, _ = fmt.Fprintf(os.Stderr, "recovering panic thread on writeBuffer function in golib/logger/hookfile/system.\nfor log file '%s'\n%v\n", p, rec)
}
libsrv.RecoveryCaller("golib/logger/hookfile/system", recover())
if h != nil {
_ = h.Close()
}
@@ -109,9 +108,7 @@ func (o *hkf) writeBuffer(buf *bytes.Buffer) error {
func (o *hkf) freeBuffer(buf *bytes.Buffer, size int) *bytes.Buffer {
defer func() {
if rec := recover(); rec != nil {
_, _ = fmt.Fprintf(os.Stderr, "recovering panic thread on freeBuffer function in golib/logger/hookfile/system.\nfor log file '%s'\n%v\n", o.getFilepath(), rec)
}
libsrv.RecoveryCaller("golib/logger/hookfile/system", recover(), fmt.Sprintf("log file: %s", o.getFilepath()))
}()
var a = o.newBuffer(o.getBufferSize())

View File

@@ -33,6 +33,8 @@ import (
"os"
"sync"
"time"
libsrv "github.com/nabbar/golib/server"
)
func (o *hks) Run(ctx context.Context) {
@@ -43,9 +45,7 @@ func (o *hks) Run(ctx context.Context) {
)
defer func() {
if rec := recover(); rec != nil {
_, _ = fmt.Fprintf(os.Stderr, "recovering panic thread on run function in golib/logger/hooksyslog/system\n%v\n", rec)
}
libsrv.RecoveryCaller("golib/logger/hooksyslog/system", recover())
if s != nil {
w.Wait()
_ = s.Close()

153
pprof/tools.go Normal file
View File

@@ -0,0 +1,153 @@
/*
* MIT License
*
* Copyright (c) 2024 Nicolas JUHEL
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*
*/
package pprof
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"runtime/pprof"
"time"
srvtck "github.com/nabbar/golib/server/runner/ticker"
)
var (
c *os.File
m string
ctx, cnl = context.WithCancel(context.Background())
s = srvtck.New(5*time.Minute, ProfilingMemRun)
)
func StartProfiling() {
ProfilingCPUStart()
ProfilingMemStart()
}
func StopProfiling() {
ProfilingMemDefer()
ProfilingCPUDefer()
}
func getPath(basename string) (*os.File, error) {
var (
h *os.File
p string
e error
)
p, e = os.Executable()
if e != nil {
return nil, e
}
p = filepath.Join(filepath.Dir(p), basename)
if _, e = os.Stat(p); e != nil && !errors.Is(e, os.ErrNotExist) {
return nil, e
} else if e != nil {
h, e = os.Create(p)
} else {
h, e = os.Open(p)
}
if e != nil {
return nil, e
}
if e = h.Truncate(0); e != nil {
_ = h.Close()
return nil, e
}
return h, nil
}
func ProfilingCPUStart() {
var e error
if c, e = getPath("cpu.prof"); e != nil {
panic(e)
} else if e = pprof.StartCPUProfile(c); e != nil {
panic(e)
}
_, _ = fmt.Fprintf(os.Stdout, "Starting pprof for CPU to file '%s'", c.Name())
}
func ProfilingCPUDefer() {
_, _ = fmt.Fprintf(os.Stdout, "Stopping pprof for CPU to file '%s'", c.Name())
pprof.StopCPUProfile()
_ = c.Close()
}
func ProfilingMemStart() {
if h, e := getPath("mem.prof"); e != nil {
panic(e)
} else {
m = h.Name()
}
if e := s.Start(ctx); e != nil {
panic(e)
}
}
func ProfilingMemRun(ctx context.Context, tck *time.Ticker) error {
if ctx.Err() != nil {
return nil
} else if len(m) < 1 {
return nil
} else if h, e := os.OpenFile(m, os.O_RDWR|os.O_EXCL|os.O_SYNC, 0644); e != nil {
return e
} else {
defer func() {
_ = h.Close()
}()
runtime.GC()
if e = pprof.WriteHeapProfile(h); e != nil {
return e
}
return nil
}
}
func ProfilingMemDefer() {
if cnl != nil {
cnl()
}
x, l := context.WithTimeout(context.Background(), 15*time.Second)
defer l()
_ = s.Stop(x)
}

View File

@@ -30,7 +30,6 @@ import (
"context"
"errors"
"fmt"
"os"
"sync/atomic"
"time"
@@ -100,9 +99,7 @@ func (o *run) Stop(ctx context.Context) error {
o.t.Store(time.Time{})
defer func() {
if rec := recover(); rec != nil {
_, _ = fmt.Fprintf(os.Stderr, "recovering panic thread on Stop function in gollib/server/startStop/model.\n%v\n", rec)
}
libsrv.RecoveryCaller("golib/server/startstop", recover())
t.Stop()
}()
@@ -143,9 +140,7 @@ func (o *run) Start(ctx context.Context) error {
o.chanInit()
defer func() {
if rec := recover(); rec != nil {
_, _ = fmt.Fprintf(os.Stderr, "recovering panic thread on Start function in gollib/server/startStop/model.\n%v\n", rec)
}
libsrv.RecoveryCaller("golib/server/startstop", recover())
_ = o.Stop(ctx)
}()

View File

@@ -29,10 +29,10 @@ package ticker
import (
"context"
"errors"
"fmt"
"os"
"sync/atomic"
"time"
libsrv "github.com/nabbar/golib/server"
)
const (
@@ -116,9 +116,7 @@ func (o *run) Start(ctx context.Context) error {
)
defer func() {
if rec := recover(); rec != nil {
_, _ = fmt.Fprintf(os.Stderr, "recovering panic thread on Start function in gollib/server/ticker/model.\n%v\n", rec)
}
libsrv.RecoveryCaller("golib/server/ticker", recover())
if n != nil {
n()
}
@@ -137,11 +135,7 @@ func (o *run) Start(ctx context.Context) error {
select {
case <-tck.C:
f := func(ctx context.Context, tck *time.Ticker) error {
defer func() {
if rec := recover(); rec != nil {
_, _ = fmt.Fprintf(os.Stderr, "recovering panic while calling function.\n%v\n", rec)
}
}()
defer libsrv.RecoveryCaller("golib/server/ticker", recover())
return o.getFunction()(ctx, tck)
}
if e := f(x, tck); e != nil {

View File

@@ -27,7 +27,11 @@
package server
import (
"bytes"
"context"
"fmt"
"os"
"runtime"
"time"
)
@@ -74,3 +78,47 @@ func RunTick(ctx context.Context, tick, max time.Duration, chk FunCheck, run Fun
}
}
}
func RecoveryCaller(proc string, rec any, data ...any) {
if rec == nil {
return
}
var (
buf = bytes.NewBuffer(make([]byte, 0))
// Set size to targetFrameIndex+2 to ensure we have room for one more caller than we need.
pCnt = make([]uintptr, 10, 255)
nCnt = runtime.Callers(1, pCnt)
)
buf.WriteString(fmt.Sprintf("Receoring process '%s': %v\n", proc, rec))
for _, d := range data {
buf.WriteString(fmt.Sprintf("%v\n", d))
}
if nCnt > 0 {
var (
frames = runtime.CallersFrames(pCnt[:nCnt])
more = true
lCnt = 0
)
for more && lCnt < 10 {
var frame runtime.Frame
frame, more = frames.Next()
if len(frame.File) > 0 {
buf.WriteString(fmt.Sprintf(" trace #%d => Line: %d - File: %s\n", lCnt, frame.Line, frame.File))
lCnt++
} else if len(frame.Function) > 0 {
buf.WriteString(fmt.Sprintf(" trace #%d => Line: %d - Func: %s\n", lCnt, frame.Line, frame.Function))
lCnt++
}
}
}
if buf.Len() > 0 {
_, _ = fmt.Fprint(os.Stderr, buf.Bytes())
}
}