mirror of
https://github.com/yusing/godoxy.git
synced 2025-07-06 22:44:03 +02:00
fix: high cpu usage
This commit is contained in:
parent
3781bb93e1
commit
b984386bab
12 changed files with 82 additions and 44 deletions
|
@ -39,6 +39,7 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
initProfiling()
|
||||||
args := common.GetArgs()
|
args := common.GetArgs()
|
||||||
|
|
||||||
switch args.Command {
|
switch args.Command {
|
||||||
|
|
5
cmd/main_production.go
Normal file
5
cmd/main_production.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
//go:build production
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
func initProfiling() {}
|
17
cmd/main_prof.go
Normal file
17
cmd/main_prof.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
//go:build pprof
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
_ "net/http/pprof"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func initProfiling() {
|
||||||
|
runtime.GOMAXPROCS(2)
|
||||||
|
go func() {
|
||||||
|
log.Println(http.ListenAndServe(":7777", nil))
|
||||||
|
}()
|
||||||
|
}
|
|
@ -23,7 +23,8 @@ type logEntryRange struct {
|
||||||
|
|
||||||
type memLogger struct {
|
type memLogger struct {
|
||||||
bytes.Buffer
|
bytes.Buffer
|
||||||
sync.Mutex
|
sync.RWMutex
|
||||||
|
notifyLock sync.RWMutex
|
||||||
connChans F.Map[chan *logEntryRange, struct{}]
|
connChans F.Map[chan *logEntryRange, struct{}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,25 +73,28 @@ func MemLogger() io.Writer {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *memLogger) Write(p []byte) (n int, err error) {
|
func (m *memLogger) Write(p []byte) (n int, err error) {
|
||||||
m.Lock()
|
m.RLock()
|
||||||
|
|
||||||
if m.Len() > maxMemLogSize {
|
if m.Len() > maxMemLogSize {
|
||||||
m.Truncate(truncateSize)
|
m.Truncate(truncateSize)
|
||||||
}
|
}
|
||||||
|
m.RUnlock()
|
||||||
|
|
||||||
pos := m.Buffer.Len()
|
|
||||||
n = len(p)
|
n = len(p)
|
||||||
|
m.Lock()
|
||||||
|
pos := m.Len()
|
||||||
_, err = m.Buffer.Write(p)
|
_, err = m.Buffer.Write(p)
|
||||||
if err != nil {
|
|
||||||
m.Unlock()
|
m.Unlock()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.connChans.Size() > 0 {
|
if m.connChans.Size() > 0 {
|
||||||
m.Unlock()
|
|
||||||
timeout := time.NewTimer(1 * time.Second)
|
timeout := time.NewTimer(1 * time.Second)
|
||||||
defer timeout.Stop()
|
defer timeout.Stop()
|
||||||
|
|
||||||
|
m.notifyLock.RLock()
|
||||||
|
defer m.notifyLock.RUnlock()
|
||||||
m.connChans.Range(func(ch chan *logEntryRange, _ struct{}) bool {
|
m.connChans.Range(func(ch chan *logEntryRange, _ struct{}) bool {
|
||||||
select {
|
select {
|
||||||
case ch <- &logEntryRange{pos, pos + n}:
|
case ch <- &logEntryRange{pos, pos + n}:
|
||||||
|
@ -102,8 +106,6 @@ func (m *memLogger) Write(p []byte) (n int, err error) {
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
m.Unlock()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,8 +122,11 @@ func (m *memLogger) ServeHTTP(config config.ConfigInstance, w http.ResponseWrite
|
||||||
/* trunk-ignore(golangci-lint/errcheck) */
|
/* trunk-ignore(golangci-lint/errcheck) */
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = conn.CloseNow()
|
_ = conn.CloseNow()
|
||||||
|
|
||||||
|
m.notifyLock.Lock()
|
||||||
m.connChans.Delete(logCh)
|
m.connChans.Delete(logCh)
|
||||||
close(logCh)
|
close(logCh)
|
||||||
|
m.notifyLock.Unlock()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err := m.wsInitial(r.Context(), conn); err != nil {
|
if err := m.wsInitial(r.Context(), conn); err != nil {
|
||||||
|
@ -149,10 +154,10 @@ func (m *memLogger) wsStreamLog(ctx context.Context, conn *websocket.Conn, ch <-
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case logRange := <-ch:
|
case logRange := <-ch:
|
||||||
m.Lock()
|
m.RLock()
|
||||||
msg := m.Buffer.Bytes()[logRange.Start:logRange.End]
|
msg := m.Buffer.Bytes()[logRange.Start:logRange.End]
|
||||||
err := m.writeBytes(ctx, conn, msg)
|
err := m.writeBytes(ctx, conn, msg)
|
||||||
m.Unlock()
|
m.RUnlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"github.com/yusing/go-proxy/internal/logging"
|
"github.com/yusing/go-proxy/internal/logging"
|
||||||
"github.com/yusing/go-proxy/internal/task"
|
"github.com/yusing/go-proxy/internal/task"
|
||||||
U "github.com/yusing/go-proxy/internal/utils"
|
U "github.com/yusing/go-proxy/internal/utils"
|
||||||
F "github.com/yusing/go-proxy/internal/utils/functional"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -27,7 +26,7 @@ type (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
clientMap F.Map[string, *SharedClient] = F.NewMapOf[string, *SharedClient]()
|
clientMap = make(map[string]*SharedClient, 5)
|
||||||
clientMapMu sync.Mutex
|
clientMapMu sync.Mutex
|
||||||
|
|
||||||
clientOptEnvHost = []client.Opt{
|
clientOptEnvHost = []client.Opt{
|
||||||
|
@ -38,11 +37,14 @@ var (
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
task.OnProgramExit("docker_clients_cleanup", func() {
|
task.OnProgramExit("docker_clients_cleanup", func() {
|
||||||
clientMap.RangeAllParallel(func(_ string, c *SharedClient) {
|
clientMapMu.Lock()
|
||||||
|
defer clientMapMu.Unlock()
|
||||||
|
|
||||||
|
for _, c := range clientMap {
|
||||||
if c.Connected() {
|
if c.Connected() {
|
||||||
c.Client.Close()
|
c.Client.Close()
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,8 +73,7 @@ func ConnectClient(host string) (*SharedClient, error) {
|
||||||
clientMapMu.Lock()
|
clientMapMu.Lock()
|
||||||
defer clientMapMu.Unlock()
|
defer clientMapMu.Unlock()
|
||||||
|
|
||||||
// check if client exists
|
if client, ok := clientMap[host]; ok {
|
||||||
if client, ok := clientMap.Load(host); ok {
|
|
||||||
client.refCount.Add()
|
client.refCount.Add()
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
@ -123,11 +124,13 @@ func ConnectClient(host string) (*SharedClient, error) {
|
||||||
}
|
}
|
||||||
c.l.Trace().Msg("client connected")
|
c.l.Trace().Msg("client connected")
|
||||||
|
|
||||||
clientMap.Store(host, c)
|
clientMap[host] = c
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
<-c.refCount.Zero()
|
<-c.refCount.Zero()
|
||||||
clientMap.Delete(c.key)
|
clientMapMu.Lock()
|
||||||
|
delete(clientMap, c.key)
|
||||||
|
clientMapMu.Unlock()
|
||||||
|
|
||||||
if c.Connected() {
|
if c.Connected() {
|
||||||
c.Client.Close()
|
c.Client.Close()
|
||||||
|
|
|
@ -42,7 +42,7 @@ const updateInterval = 2 * time.Hour
|
||||||
|
|
||||||
var (
|
var (
|
||||||
iconsCache *Cache
|
iconsCache *Cache
|
||||||
iconsCahceMu sync.Mutex
|
iconsCahceMu sync.RWMutex
|
||||||
lastUpdate time.Time
|
lastUpdate time.Time
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -71,14 +71,17 @@ func InitIconListCache() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListAvailableIcons() (*Cache, error) {
|
func ListAvailableIcons() (*Cache, error) {
|
||||||
iconsCahceMu.Lock()
|
iconsCahceMu.RLock()
|
||||||
defer iconsCahceMu.Unlock()
|
|
||||||
|
|
||||||
if time.Since(lastUpdate) < updateInterval {
|
if time.Since(lastUpdate) < updateInterval {
|
||||||
if !iconsCache.needUpdate() {
|
if !iconsCache.needUpdate() {
|
||||||
|
iconsCahceMu.RUnlock()
|
||||||
return iconsCache, nil
|
return iconsCache, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
iconsCahceMu.RUnlock()
|
||||||
|
|
||||||
|
iconsCahceMu.Lock()
|
||||||
|
defer iconsCahceMu.Unlock()
|
||||||
|
|
||||||
icons, err := fetchIconData()
|
icons, err := fetchIconData()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -19,7 +19,7 @@ type (
|
||||||
io AccessLogIO
|
io AccessLogIO
|
||||||
|
|
||||||
buf bytes.Buffer // buffer for non-flushed log
|
buf bytes.Buffer // buffer for non-flushed log
|
||||||
bufMu sync.Mutex // protect buf
|
bufMu sync.RWMutex
|
||||||
bufPool sync.Pool // buffer pool for formatting a single log line
|
bufPool sync.Pool // buffer pool for formatting a single log line
|
||||||
|
|
||||||
flushThreshold int
|
flushThreshold int
|
||||||
|
@ -123,10 +123,10 @@ func (l *AccessLogger) Flush(force bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if force || l.buf.Len() >= l.flushThreshold {
|
if force || l.buf.Len() >= l.flushThreshold {
|
||||||
l.bufMu.Lock()
|
l.bufMu.RLock()
|
||||||
l.write(l.buf.Bytes())
|
l.write(l.buf.Bytes())
|
||||||
l.buf.Reset()
|
l.buf.Reset()
|
||||||
l.bufMu.Unlock()
|
l.bufMu.RUnlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,19 +19,12 @@ import (
|
||||||
const errPagesBasePath = common.ErrorPagesBasePath
|
const errPagesBasePath = common.ErrorPagesBasePath
|
||||||
|
|
||||||
var (
|
var (
|
||||||
setupMu sync.Mutex
|
setupOnce sync.Once
|
||||||
dirWatcher W.Watcher
|
dirWatcher W.Watcher
|
||||||
fileContentMap = F.NewMapOf[string, []byte]()
|
fileContentMap = F.NewMapOf[string, []byte]()
|
||||||
)
|
)
|
||||||
|
|
||||||
func setup() {
|
func setup() {
|
||||||
setupMu.Lock()
|
|
||||||
defer setupMu.Unlock()
|
|
||||||
|
|
||||||
if dirWatcher != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
t := task.RootTask("error_page", false)
|
t := task.RootTask("error_page", false)
|
||||||
dirWatcher = W.NewDirectoryWatcher(t, errPagesBasePath)
|
dirWatcher = W.NewDirectoryWatcher(t, errPagesBasePath)
|
||||||
loadContent()
|
loadContent()
|
||||||
|
@ -39,7 +32,7 @@ func setup() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetStaticFile(filename string) ([]byte, bool) {
|
func GetStaticFile(filename string) ([]byte, bool) {
|
||||||
setup()
|
setupOnce.Do(setup)
|
||||||
return fileContentMap.Load(filename)
|
return fileContentMap.Load(filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,8 +49,6 @@ func (rl *rateLimiter) newLimiter() *rate.Limiter {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *rateLimiter) limit(w http.ResponseWriter, r *http.Request) bool {
|
func (rl *rateLimiter) limit(w http.ResponseWriter, r *http.Request) bool {
|
||||||
rl.mu.Lock()
|
|
||||||
|
|
||||||
host, _, err := net.SplitHostPort(r.RemoteAddr)
|
host, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rl.AddTracef("unable to parse remote address %s", r.RemoteAddr)
|
rl.AddTracef("unable to parse remote address %s", r.RemoteAddr)
|
||||||
|
@ -58,12 +56,12 @@ func (rl *rateLimiter) limit(w http.ResponseWriter, r *http.Request) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rl.mu.Lock()
|
||||||
limiter, ok := rl.requestMap[host]
|
limiter, ok := rl.requestMap[host]
|
||||||
if !ok {
|
if !ok {
|
||||||
limiter = rl.newLimiter()
|
limiter = rl.newLimiter()
|
||||||
rl.requestMap[host] = limiter
|
rl.requestMap[host] = limiter
|
||||||
}
|
}
|
||||||
|
|
||||||
rl.mu.Unlock()
|
rl.mu.Unlock()
|
||||||
|
|
||||||
if limiter.Allow() {
|
if limiter.Allow() {
|
||||||
|
|
|
@ -172,7 +172,7 @@ func (e *RawEntry) Finalize() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Homepage == nil {
|
if e.Homepage.IsEmpty() {
|
||||||
e.Homepage = homepage.NewItem(e.Alias)
|
e.Homepage = homepage.NewItem(e.Alias)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
|
@ -45,7 +46,10 @@ type (
|
||||||
callbacksDone chan struct{}
|
callbacksDone chan struct{}
|
||||||
|
|
||||||
finished chan struct{}
|
finished chan struct{}
|
||||||
finishedCalled bool
|
// finishedCalled == 1 Finish has been called
|
||||||
|
// but does not mean that the task is finished yet
|
||||||
|
// this is used to avoid calling Finish twice
|
||||||
|
finishedCalled uint32
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
|
||||||
|
@ -93,13 +97,19 @@ func (t *Task) OnCancel(about string, fn func()) {
|
||||||
// Finish cancel all subtasks and wait for them to finish,
|
// Finish cancel all subtasks and wait for them to finish,
|
||||||
// then marks the task as finished, with the given reason (if any).
|
// then marks the task as finished, with the given reason (if any).
|
||||||
func (t *Task) Finish(reason any) {
|
func (t *Task) Finish(reason any) {
|
||||||
|
if atomic.LoadUint32(&t.finishedCalled) == 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
t.mu.Lock()
|
t.mu.Lock()
|
||||||
if t.finishedCalled {
|
if t.finishedCalled == 1 {
|
||||||
t.mu.Unlock()
|
t.mu.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
t.finishedCalled = true
|
|
||||||
|
t.finishedCalled = 1
|
||||||
t.mu.Unlock()
|
t.mu.Unlock()
|
||||||
|
|
||||||
t.finish(reason)
|
t.finish(reason)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -116,6 +116,9 @@ GoDoxy v0.8.2 expected changes
|
||||||
```
|
```
|
||||||
|
|
||||||
- **new** Brand new rewritten WebUI
|
- **new** Brand new rewritten WebUI
|
||||||
|
- View logs directly from WebUI
|
||||||
|
- Edit dashboard item config (overrides docker labels and include file)
|
||||||
|
- Health bubbles, latency, etc. rich info on dashboard items
|
||||||
- **new** Support selfh.st icons: `@selfhst/<reference>.<format>` _(e.g. `@selfhst/adguard-home.webp`)_
|
- **new** Support selfh.st icons: `@selfhst/<reference>.<format>` _(e.g. `@selfhst/adguard-home.webp`)_
|
||||||
- also uses the display name on https://selfh.st/icons/ as default for our dashboard!
|
- also uses the display name on https://selfh.st/icons/ as default for our dashboard!
|
||||||
- **new** GoDoxy server side favicon retreiving and caching
|
- **new** GoDoxy server side favicon retreiving and caching
|
||||||
|
|
Loading…
Add table
Reference in a new issue