118 lines
2.2 KiB
Go
118 lines
2.2 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"os/exec"
|
|
|
|
"git.jingrow.com/cilium/ebpf/perf"
|
|
)
|
|
|
|
// CircularBuffer to keep fixed-size samples
|
|
|
|
type CircularBuffer[T any] struct {
|
|
mu sync.RWMutex
|
|
data []T
|
|
head int
|
|
full bool
|
|
size int
|
|
}
|
|
|
|
func NewCircularBuffer[T any](size int) *CircularBuffer[T] {
|
|
return &CircularBuffer[T]{
|
|
data: make([]T, size),
|
|
size: size,
|
|
}
|
|
}
|
|
|
|
func (c *CircularBuffer[T]) Add(s T) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.data[c.head] = s
|
|
c.head = (c.head + 1) % c.size
|
|
if c.head == 0 {
|
|
c.full = true
|
|
}
|
|
}
|
|
|
|
func (c *CircularBuffer[T]) GetLatest() *T {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
|
|
if len(c.data) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Get most recent item (just before head)
|
|
idx := c.head - 1
|
|
if idx < 0 {
|
|
idx = len(c.data) - 1
|
|
}
|
|
return &c.data[idx]
|
|
}
|
|
|
|
func (c *CircularBuffer[T]) Snapshot() []T {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
if !c.full && c.head == 0 {
|
|
return nil
|
|
}
|
|
var out []T
|
|
if c.full {
|
|
out = append(out, c.data[c.head:]...)
|
|
out = append(out, c.data[:c.head]...)
|
|
} else {
|
|
out = append(out, c.data[:c.head]...)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Helper methods
|
|
|
|
func isPerfClosedError(err error) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
if err == perf.ErrClosed {
|
|
return true
|
|
}
|
|
e := strings.ToLower(err.Error())
|
|
if (strings.Contains(e, "perf reader closed") ||
|
|
strings.Contains(e, "reader is closed") ||
|
|
strings.Contains(e, "closed") && strings.Contains(e, "perf")) ||
|
|
strings.Contains(strings.ToLower(err.Error()), "file already closed") {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// func cStr(b []byte) string {
|
|
// if i := bytes.IndexByte(b, 0); i >= 0 {
|
|
// return string(b[:i])
|
|
// }
|
|
// return string(b)
|
|
// }
|
|
|
|
func captureMariaDBProcessList() (string, error) {
|
|
// max 10s timeout
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
cmd := exec.CommandContext(ctx, "mariadb", "-e", "SHOW FULL PROCESSLIST\\G")
|
|
output, err := cmd.CombinedOutput()
|
|
if ctx.Err() == context.DeadlineExceeded {
|
|
return "", errors.New("mariadb process list command timed out")
|
|
}
|
|
|
|
if err != nil {
|
|
return "", fmt.Errorf("error executing mariadb command: %v, output: %s", err, string(output))
|
|
}
|
|
|
|
return string(output), nil
|
|
}
|