mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-11 02:15:48 +02:00
refactor(analytic): improved network stat #913
This commit is contained in:
parent
ec29a77f99
commit
789698a0d4
6 changed files with 247 additions and 108 deletions
|
@ -11,7 +11,6 @@ import (
|
|||
"github.com/shirou/gopsutil/v4/cpu"
|
||||
"github.com/shirou/gopsutil/v4/host"
|
||||
"github.com/shirou/gopsutil/v4/load"
|
||||
"github.com/shirou/gopsutil/v4/net"
|
||||
"github.com/spf13/cast"
|
||||
"github.com/uozi-tech/cosy/logger"
|
||||
|
||||
|
@ -75,15 +74,13 @@ func Analytic(c *gin.Context) {
|
|||
continue
|
||||
}
|
||||
|
||||
network, err := net.IOCounters(false)
|
||||
network, err := analytic.GetNetworkStat()
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(network) > 0 {
|
||||
stat.Network = network[0]
|
||||
}
|
||||
stat.Network = *network
|
||||
|
||||
// write
|
||||
err = ws.WriteJSON(stat)
|
||||
|
@ -104,7 +101,7 @@ func GetAnalyticInit(c *gin.Context) {
|
|||
logger.Error(err)
|
||||
}
|
||||
|
||||
network, err := net.IOCounters(false)
|
||||
network, err := analytic.GetNetworkStat()
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
|
@ -119,11 +116,6 @@ func GetAnalyticInit(c *gin.Context) {
|
|||
logger.Error(err)
|
||||
}
|
||||
|
||||
var _net net.IOCountersStat
|
||||
if len(network) > 0 {
|
||||
_net = network[0]
|
||||
}
|
||||
|
||||
hostInfo, err := host.Info()
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
|
@ -151,7 +143,7 @@ func GetAnalyticInit(c *gin.Context) {
|
|||
Total: analytic.CpuTotalRecord,
|
||||
},
|
||||
Network: NetworkRecords{
|
||||
Init: _net,
|
||||
Init: *network,
|
||||
BytesRecv: analytic.NetRecvRecord,
|
||||
BytesSent: analytic.NetSentRecord,
|
||||
},
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package analytic
|
||||
|
||||
import (
|
||||
"github.com/shirou/gopsutil/v4/net"
|
||||
"github.com/uozi-tech/cosy/logger"
|
||||
"time"
|
||||
|
||||
"github.com/uozi-tech/cosy/logger"
|
||||
)
|
||||
|
||||
type Usage[T uint64 | float64] struct {
|
||||
|
@ -25,14 +25,12 @@ var (
|
|||
)
|
||||
|
||||
func init() {
|
||||
network, err := net.IOCounters(false)
|
||||
network, err := GetNetworkStat()
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
if len(network) > 0 {
|
||||
LastNetRecv = network[0].BytesRecv
|
||||
LastNetSent = network[0].BytesSent
|
||||
}
|
||||
LastNetRecv = network.BytesRecv
|
||||
LastNetSent = network.BytesSent
|
||||
|
||||
LastDiskReads, LastDiskWrites = getTotalDiskIO()
|
||||
|
||||
|
|
220
internal/analytic/network.go
Normal file
220
internal/analytic/network.go
Normal file
|
@ -0,0 +1,220 @@
|
|||
package analytic
|
||||
|
||||
import (
|
||||
stdnet "net"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/net"
|
||||
"github.com/uozi-tech/cosy/logger"
|
||||
)
|
||||
|
||||
func GetNetworkStat() (data *net.IOCountersStat, err error) {
|
||||
networkStats, err := net.IOCounters(true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(networkStats) == 0 {
|
||||
return &net.IOCountersStat{}, nil
|
||||
}
|
||||
// Get all network interfaces
|
||||
interfaces, err := stdnet.Interfaces()
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
totalBytesRecv uint64
|
||||
totalBytesSent uint64
|
||||
totalPacketsRecv uint64
|
||||
totalPacketsSent uint64
|
||||
totalErrIn uint64
|
||||
totalErrOut uint64
|
||||
totalDropIn uint64
|
||||
totalDropOut uint64
|
||||
totalFifoIn uint64
|
||||
totalFifoOut uint64
|
||||
)
|
||||
|
||||
// Create a map of external interface names
|
||||
externalInterfaces := make(map[string]bool)
|
||||
|
||||
// Identify external interfaces
|
||||
for _, iface := range interfaces {
|
||||
// Skip down or loopback interfaces
|
||||
if iface.Flags&stdnet.FlagUp == 0 ||
|
||||
iface.Flags&stdnet.FlagLoopback != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get addresses for this interface
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip interfaces without addresses
|
||||
if len(addrs) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for non-private IP addresses
|
||||
for _, addr := range addrs {
|
||||
ip, ipNet, err := stdnet.ParseCIDR(addr.String())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip virtual, local, multicast, and special purpose IPs
|
||||
if !isRealExternalIP(ip, ipNet) {
|
||||
continue
|
||||
}
|
||||
|
||||
externalInterfaces[iface.Name] = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Accumulate stats only from external interfaces
|
||||
for _, stat := range networkStats {
|
||||
if externalInterfaces[stat.Name] {
|
||||
totalBytesRecv += stat.BytesRecv
|
||||
totalBytesSent += stat.BytesSent
|
||||
totalPacketsRecv += stat.PacketsRecv
|
||||
totalPacketsSent += stat.PacketsSent
|
||||
totalErrIn += stat.Errin
|
||||
totalErrOut += stat.Errout
|
||||
totalDropIn += stat.Dropin
|
||||
totalDropOut += stat.Dropout
|
||||
totalFifoIn += stat.Fifoin
|
||||
totalFifoOut += stat.Fifoout
|
||||
}
|
||||
}
|
||||
|
||||
return &net.IOCountersStat{
|
||||
Name: "analytic.network",
|
||||
BytesRecv: totalBytesRecv,
|
||||
BytesSent: totalBytesSent,
|
||||
PacketsRecv: totalPacketsRecv,
|
||||
PacketsSent: totalPacketsSent,
|
||||
Errin: totalErrIn,
|
||||
Errout: totalErrOut,
|
||||
Dropin: totalDropIn,
|
||||
Dropout: totalDropOut,
|
||||
Fifoin: totalFifoIn,
|
||||
Fifoout: totalFifoOut,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// isRealExternalIP checks if an IP is a genuine external (public) IP
|
||||
func isRealExternalIP(ip stdnet.IP, ipNet *stdnet.IPNet) bool {
|
||||
// Skip if it's not a global unicast address
|
||||
if !ip.IsGlobalUnicast() {
|
||||
return false
|
||||
}
|
||||
|
||||
// Skip private IPs
|
||||
if ip.IsPrivate() {
|
||||
return false
|
||||
}
|
||||
|
||||
// Skip link-local addresses
|
||||
if ip.IsLinkLocalUnicast() {
|
||||
return false
|
||||
}
|
||||
|
||||
// Skip loopback
|
||||
if ip.IsLoopback() {
|
||||
return false
|
||||
}
|
||||
|
||||
// Skip multicast
|
||||
if ip.IsMulticast() {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check for special reserved ranges
|
||||
if isReservedIP(ip) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// isReservedIP checks if an IP belongs to special reserved ranges
|
||||
func isReservedIP(ip stdnet.IP) bool {
|
||||
// Handle IPv4
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
// TEST-NET-1: 192.0.2.0/24 (RFC 5737)
|
||||
if ip4[0] == 192 && ip4[1] == 0 && ip4[2] == 2 {
|
||||
return true
|
||||
}
|
||||
|
||||
// TEST-NET-2: 198.51.100.0/24 (RFC 5737)
|
||||
if ip4[0] == 198 && ip4[1] == 51 && ip4[2] == 100 {
|
||||
return true
|
||||
}
|
||||
|
||||
// TEST-NET-3: 203.0.113.0/24 (RFC 5737)
|
||||
if ip4[0] == 203 && ip4[1] == 0 && ip4[2] == 113 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Benchmark tests: 198.18.0.0/15 (includes 198.19.0.0/16) (RFC 2544)
|
||||
if ip4[0] == 198 && (ip4[1] == 18 || ip4[1] == 19) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Documentation: 240.0.0.0/4 (RFC 1112)
|
||||
if ip4[0] >= 240 {
|
||||
return true
|
||||
}
|
||||
|
||||
// CGNAT: 100.64.0.0/10 (RFC 6598)
|
||||
if ip4[0] == 100 && (ip4[1]&0xC0) == 64 {
|
||||
return true
|
||||
}
|
||||
} else if ip.To16() != nil {
|
||||
// Documentation prefix (2001:db8::/32) - RFC 3849
|
||||
if ip[0] == 0x20 && ip[1] == 0x01 && ip[2] == 0x0d && ip[3] == 0xb8 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Unique Local Addresses (fc00::/7) - RFC 4193
|
||||
if (ip[0] & 0xfe) == 0xfc {
|
||||
return true
|
||||
}
|
||||
|
||||
// 6to4 relay (2002::/16) - RFC 3056
|
||||
if ip[0] == 0x20 && ip[1] == 0x02 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Teredo tunneling (2001:0::/32) - RFC 4380
|
||||
if ip[0] == 0x20 && ip[1] == 0x01 && ip[2] == 0x00 && ip[3] == 0x00 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Deprecated site-local addresses (fec0::/10) - RFC 3879
|
||||
if (ip[0]&0xff) == 0xfe && (ip[1]&0xc0) == 0xc0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Old 6bone addresses (3ffe::/16) - Deprecated
|
||||
if ip[0] == 0x3f && ip[1] == 0xfe {
|
||||
return true
|
||||
}
|
||||
|
||||
// ORCHID addresses (2001:10::/28) - RFC 4843
|
||||
if ip[0] == 0x20 && ip[1] == 0x01 && ip[2] == 0x00 && (ip[3]&0xf0) == 0x10 {
|
||||
return true
|
||||
}
|
||||
|
||||
// ORCHID v2 addresses (2001:20::/28) - RFC 7343
|
||||
if ip[0] == 0x20 && ip[1] == 0x01 && ip[2] == 0x00 && (ip[3]&0xf0) == 0x20 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
package analytic
|
||||
|
||||
import (
|
||||
"github.com/shirou/gopsutil/v4/cpu"
|
||||
"github.com/shirou/gopsutil/v4/load"
|
||||
"github.com/shirou/gopsutil/v4/net"
|
||||
"github.com/uozi-tech/cosy/logger"
|
||||
"math"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/cpu"
|
||||
"github.com/shirou/gopsutil/v4/load"
|
||||
"github.com/uozi-tech/cosy/logger"
|
||||
)
|
||||
|
||||
func GetNodeStat() (data NodeStat) {
|
||||
|
@ -37,22 +37,17 @@ func GetNodeStat() (data NodeStat) {
|
|||
return
|
||||
}
|
||||
|
||||
netIO, err := net.IOCounters(false)
|
||||
network, err := GetNetworkStat()
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
var network net.IOCountersStat
|
||||
if len(netIO) > 0 {
|
||||
network = netIO[0]
|
||||
}
|
||||
|
||||
return NodeStat{
|
||||
AvgLoad: loadAvg,
|
||||
CPUPercent: math.Min((cpuUserUsage+cpuSystemUsage)*100, 100),
|
||||
MemoryPercent: memory.Pressure,
|
||||
DiskPercent: diskStat.Percentage,
|
||||
Network: network,
|
||||
Network: *network,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
package analytic
|
||||
|
||||
import (
|
||||
stdnet "net"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/cpu"
|
||||
"github.com/shirou/gopsutil/v4/disk"
|
||||
"github.com/shirou/gopsutil/v4/net"
|
||||
"github.com/uozi-tech/cosy/logger"
|
||||
)
|
||||
|
||||
|
@ -71,87 +69,22 @@ func recordCpu(now time.Time) {
|
|||
|
||||
func recordNetwork(now time.Time) {
|
||||
// Get separate statistics for each interface
|
||||
networkStats, err := net.IOCounters(true)
|
||||
networkStats, err := GetNetworkStat()
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(networkStats) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Get all network interfaces
|
||||
interfaces, err := stdnet.Interfaces()
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
var totalBytesRecv uint64
|
||||
var totalBytesSent uint64
|
||||
var externalInterfaceFound bool
|
||||
|
||||
// Iterate through all interfaces to find external ones
|
||||
for _, iface := range interfaces {
|
||||
// Skip interfaces that are down
|
||||
if iface.Flags&stdnet.FlagUp == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get IP addresses for the interface
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if this is an external interface
|
||||
for _, addr := range addrs {
|
||||
if ipNet, ok := addr.(*stdnet.IPNet); ok {
|
||||
// Exclude loopback addresses and private IPs
|
||||
if !ipNet.IP.IsLoopback() {
|
||||
// Found external interface, accumulate its statistics
|
||||
for _, stat := range networkStats {
|
||||
if stat.Name == iface.Name {
|
||||
totalBytesRecv += stat.BytesRecv
|
||||
totalBytesSent += stat.BytesSent
|
||||
externalInterfaceFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no external interface is found, use fallback option
|
||||
if !externalInterfaceFound {
|
||||
// Fallback: use all non-loopback interfaces
|
||||
for _, iface := range interfaces {
|
||||
if iface.Flags&stdnet.FlagLoopback == 0 && iface.Flags&stdnet.FlagUp != 0 {
|
||||
for _, stat := range networkStats {
|
||||
if stat.Name == iface.Name {
|
||||
totalBytesRecv += stat.BytesRecv
|
||||
totalBytesSent += stat.BytesSent
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LastNetRecv = totalBytesRecv
|
||||
LastNetSent = totalBytesSent
|
||||
LastNetRecv = networkStats.BytesRecv
|
||||
LastNetSent = networkStats.BytesSent
|
||||
|
||||
NetRecvRecord = append(NetRecvRecord, Usage[uint64]{
|
||||
Time: now,
|
||||
Usage: totalBytesRecv - LastNetRecv,
|
||||
Usage: networkStats.BytesRecv - LastNetRecv,
|
||||
})
|
||||
NetSentRecord = append(NetSentRecord, Usage[uint64]{
|
||||
Time: now,
|
||||
Usage: totalBytesSent - LastNetSent,
|
||||
Usage: networkStats.BytesSent - LastNetSent,
|
||||
})
|
||||
|
||||
if len(NetRecvRecord) > 100 {
|
||||
|
|
|
@ -2,14 +2,15 @@ package test
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/0xJacky/Nginx-UI/internal/analytic"
|
||||
"github.com/shirou/gopsutil/v4/cpu"
|
||||
"github.com/shirou/gopsutil/v4/disk"
|
||||
"github.com/shirou/gopsutil/v4/load"
|
||||
"github.com/shirou/gopsutil/v4/mem"
|
||||
"github.com/shirou/gopsutil/v4/net"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGoPsutil(t *testing.T) {
|
||||
|
@ -35,9 +36,9 @@ func TestGoPsutil(t *testing.T) {
|
|||
diskUsage, _ := disk.Usage(".")
|
||||
fmt.Println(diskUsage.String())
|
||||
|
||||
network, _ := net.IOCounters(false)
|
||||
network, _ := analytic.GetNetworkStat()
|
||||
fmt.Println(network)
|
||||
time.Sleep(time.Second)
|
||||
network, _ = net.IOCounters(false)
|
||||
network, _ = analytic.GetNetworkStat()
|
||||
fmt.Println(network)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue