Skip to content

Commit d7b8622

Browse files
committed
feat: Update fature container name & container image. Network restransmit metrics
1 parent a7d7349 commit d7b8622

1 file changed

Lines changed: 131 additions & 0 deletions

File tree

internal/collector/system/host.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,11 @@ type systemInfoCache struct {
343343
info *collector.SystemInfo
344344
timestamp time.Time
345345
ttl time.Duration
346+
347+
// Network rate tracking
348+
prevNetBytesSent uint64
349+
prevNetBytesRecv uint64
350+
prevNetTimestamp time.Time
346351
}
347352

348353
var infoCache = &systemInfoCache{
@@ -497,6 +502,25 @@ func (c *HostCollector) GetSystemInfo() (*collector.SystemInfo, error) {
497502
info.SwapOut = swapInfo.Sout
498503
}
499504

505+
// Page faults from /proc/vmstat (Linux only)
506+
if runtime.GOOS == "linux" {
507+
if data, err := os.ReadFile("/proc/vmstat"); err == nil {
508+
var totalFaults uint64
509+
for _, line := range strings.Split(string(data), "\n") {
510+
switch {
511+
case strings.HasPrefix(line, "pgmajfault "):
512+
info.PageFaultsMajor = parseUint64(strings.TrimPrefix(line, "pgmajfault "))
513+
case strings.HasPrefix(line, "pgfault "):
514+
totalFaults = parseUint64(strings.TrimPrefix(line, "pgfault "))
515+
}
516+
}
517+
// Minor faults = total faults - major faults
518+
if totalFaults > info.PageFaultsMajor {
519+
info.PageFaultsMinor = totalFaults - info.PageFaultsMajor
520+
}
521+
}
522+
}
523+
500524
// ==========================================================================
501525
// Disk Information
502526
// ==========================================================================
@@ -553,6 +577,14 @@ func (c *HostCollector) GetSystemInfo() (*collector.SystemInfo, error) {
553577
if totalWriteOps > 0 && totalWriteTime > 0 {
554578
info.DiskLatencyWrite = float64(totalWriteTime) / float64(totalWriteOps)
555579
}
580+
581+
// Calculate IOPS (operations per second based on IO time)
582+
// DiskIOTime is in milliseconds, represents time spent doing I/O
583+
totalOps := totalReadOps + totalWriteOps
584+
if totalOps > 0 && totalIOTime > 0 {
585+
// IOPS = total operations / (IO time in seconds)
586+
info.DiskIOPS = float64(totalOps) / (float64(totalIOTime) / 1000.0)
587+
}
556588
}
557589

558590
// Per-partition disk info
@@ -595,6 +627,24 @@ func (c *HostCollector) GetSystemInfo() (*collector.SystemInfo, error) {
595627
info.NetworkDropsOut = total.Dropout
596628
info.NetworkFifoIn = total.Fifoin
597629
info.NetworkFifoOut = total.Fifoout
630+
631+
// Calculate network rates using cached previous values
632+
now := time.Now()
633+
infoCache.mu.Lock()
634+
if !infoCache.prevNetTimestamp.IsZero() {
635+
elapsed := now.Sub(infoCache.prevNetTimestamp).Seconds()
636+
if elapsed > 0 && total.BytesSent >= infoCache.prevNetBytesSent {
637+
info.NetworkBytesSentRate = float64(total.BytesSent-infoCache.prevNetBytesSent) / elapsed
638+
}
639+
if elapsed > 0 && total.BytesRecv >= infoCache.prevNetBytesRecv {
640+
info.NetworkBytesRecvRate = float64(total.BytesRecv-infoCache.prevNetBytesRecv) / elapsed
641+
}
642+
}
643+
// Update previous values for next calculation
644+
infoCache.prevNetBytesSent = total.BytesSent
645+
infoCache.prevNetBytesRecv = total.BytesRecv
646+
infoCache.prevNetTimestamp = now
647+
infoCache.mu.Unlock()
598648
}
599649

600650
// TCP connection states
@@ -626,6 +676,33 @@ func (c *HostCollector) GetSystemInfo() (*collector.SystemInfo, error) {
626676
}
627677
}
628678

679+
// TCP Retransmits from /proc/net/snmp (Linux only)
680+
if runtime.GOOS == "linux" {
681+
if data, err := os.ReadFile("/proc/net/snmp"); err == nil {
682+
lines := strings.Split(string(data), "\n")
683+
for i, line := range lines {
684+
// Find the Tcp: header line
685+
if strings.HasPrefix(line, "Tcp:") && !strings.Contains(line, " ") == false {
686+
// Check if this is the header line (contains column names)
687+
if strings.Contains(line, "RtoAlgorithm") {
688+
// Next line contains values
689+
if i+1 < len(lines) {
690+
values := strings.Fields(lines[i+1])
691+
// RetransSegs is at index 12 (0-indexed)
692+
// Columns: RtoAlgorithm RtoMin RtoMax MaxConn ActiveOpens PassiveOpens
693+
// AttemptFails EstabResets CurrEstab InSegs OutSegs RetransSegs
694+
// InErrs OutRsts InCsumErrors
695+
if len(values) > 12 {
696+
info.TCPRetransmits = parseUint64(values[12])
697+
}
698+
}
699+
break
700+
}
701+
}
702+
}
703+
}
704+
}
705+
629706
// Per-interface network info
630707
netInterfaces, err := net.Interfaces()
631708
if err == nil {
@@ -751,6 +828,17 @@ func (c *HostCollector) GetSystemInfo() (*collector.SystemInfo, error) {
751828
}
752829
}
753830
}
831+
832+
// System calls - aggregate from all processes' /proc/[pid]/io
833+
// This counts read (syscr) and write (syscw) system calls
834+
if procs != nil {
835+
for _, p := range procs {
836+
ioCounters, err := p.IOCounters()
837+
if err == nil {
838+
info.SystemCalls += ioCounters.ReadCount + ioCounters.WriteCount
839+
}
840+
}
841+
}
754842
}
755843

756844
// ==========================================================================
@@ -760,6 +848,8 @@ func (c *HostCollector) GetSystemInfo() (*collector.SystemInfo, error) {
760848
if info.IsContainer {
761849
info.ContainerID = getContainerID()
762850
info.ContainerRuntime = detectContainerRuntime()
851+
info.ContainerName = getContainerName()
852+
info.ContainerImage = getContainerImage()
763853
}
764854

765855
info.IsVirtualized, info.VirtualizationType = detectVirtualization()
@@ -1007,3 +1097,44 @@ func getHostnameFallback() string {
10071097
}
10081098
return "unknown"
10091099
}
1100+
1101+
// getContainerName returns the container name from environment variables
1102+
func getContainerName() string {
1103+
// Check common environment variables for container name
1104+
// Kubernetes sets HOSTNAME to pod name
1105+
if name := os.Getenv("CONTAINER_NAME"); name != "" {
1106+
return name
1107+
}
1108+
// Docker Compose sets COMPOSE_PROJECT_NAME and service name
1109+
if project := os.Getenv("COMPOSE_PROJECT_NAME"); project != "" {
1110+
if service := os.Getenv("COMPOSE_SERVICE"); service != "" {
1111+
return project + "_" + service
1112+
}
1113+
}
1114+
// Kubernetes pod name
1115+
if podName := os.Getenv("POD_NAME"); podName != "" {
1116+
return podName
1117+
}
1118+
// Try to get from Docker environment
1119+
if name := os.Getenv("DOCKER_CONTAINER_NAME"); name != "" {
1120+
return name
1121+
}
1122+
return ""
1123+
}
1124+
1125+
// getContainerImage returns the container image from environment variables
1126+
func getContainerImage() string {
1127+
// Check common environment variables for container image
1128+
if image := os.Getenv("CONTAINER_IMAGE"); image != "" {
1129+
return image
1130+
}
1131+
// Kubernetes commonly uses this downward API field
1132+
if image := os.Getenv("POD_IMAGE"); image != "" {
1133+
return image
1134+
}
1135+
// Docker environment variable
1136+
if image := os.Getenv("DOCKER_IMAGE"); image != "" {
1137+
return image
1138+
}
1139+
return ""
1140+
}

0 commit comments

Comments
 (0)