Skip to content

Commit

Permalink
Merge pull request #1 from EmaLinuxawy/v2
Browse files Browse the repository at this point in the history
Release Version 2.0.0
  • Loading branch information
EmaLinuxawy authored Jan 8, 2024
2 parents 017272d + 3961cda commit f9c7fc6
Show file tree
Hide file tree
Showing 16 changed files with 604 additions and 246 deletions.
51 changes: 24 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
# Monitor X

## Introduction

Monitor X is a monitoring tool that gets various system metrics, developed in Golang.
Monitor-X is a system monitoring tool that provides real-time insights into various aspects of system performance, including CPU usage, memory utilization, disk usage, network statistics, and more. Built with Go and termui, it offers a visually appealing and easy-to-navigate terminal-based user interface.

## Features

- Load Average
- Total CPU Usage
- Individual CPU Percentages
- Memory Usage
- **CPU Monitoring**: Track CPU usage per core, total CPU usage, and top CPU-consuming processes.
- **Memory Usage**: Monitor total, used, and free memory in real-time.
- **Disk Usage**: Keep an eye on disk space utilization.
- **Network Statistics**: View detailed network interface statistics, including bytes sent/received.
- **Load Average**: Check system load average over 1, 5, and 15 minutes.
- **User-friendly Interface**: Navigable terminal-based UI for easy monitoring.


This tool is built to be lightweight, efficient, and easy to integrate into existing monitoring solutions.

## Installation

To install Monitor X, ensure that you have Golang installed on your system. Follow these steps:

1. Clone the repository:

```shell
```bash
git clone https://github.com/EmaLinuxawy/monitor-x.git
```
2. Navigate to the cloned directory:
Expand All @@ -33,23 +33,20 @@ To install Monitor X, ensure that you have Golang installed on your system. Foll
go build .
```

4. Run the application:

```bash
./monitor-x
```

## Usage

```bash
./monitor-x
```

### Functions

1. **getLoadAverage** :
Retrieves the system's load average, a measure of system activity, giving an idea of how busy the system is.
2. **getTotalCPUUsage** :
Calculates the total CPU usage percentage, providing a quick overview of CPU utilization.
3. **getCPUPercentages** :
Lists the usage percentage of each CPU, helpful for identifying uneven load distribution across CPUs.
4. **printCPUstats** :
Prints detailed CPU statistics in a human-readable format, ideal for logging and monitoring dashboards.
5. **getMemoryUsed** :
Reports the amount of memory currently being used, essential for detecting memory leaks or high memory consumption.
6. **main** :
The entry point of the application, orchestrating the monitoring processes and outputting the data.
After starting Monitor-X, use the following keyboard shortcuts to navigate through the application:

- `q` or `<Ctrl-C>`: Quit the application.
- `<Up>` and `<Down>`: Scroll through the lists.
- `<Enter>`: Expand or collapse sections (if applicable).

## Contributing

Contributions to Monitor-X are welcome! Please feel free to submit pull requests, report bugs, or suggest features through the [GitHub repository](https://github.com/Emalinuxawy/monitor-x).
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module cpu
module github.com/emaLinuxawy/monitor-x

go 1.21.4

Expand All @@ -10,7 +10,7 @@ require (
require (
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mattn/go-runewidth v0.0.2 // indirect
github.com/mattn/go-runewidth v0.0.7 // indirect
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
Expand Down
3 changes: 2 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d h1:x3S6kxmy49zXVVyhcnrFqxvNVCBPb2KZ9hV2RBdS840=
Expand Down
270 changes: 54 additions & 216 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,227 +2,65 @@ package main

import (
"context"
"fmt"
"os"
"sort"
"text/tabwriter"
"log"
"sync"
"time"

"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/load"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v3/net"
"github.com/shirou/gopsutil/v3/process"
"github.com/emaLinuxawy/monitor-x/uiupdate"
"github.com/emaLinuxawy/monitor-x/view"
ui "github.com/gizak/termui/v3"
)

type ProcessInfo struct {
Proc *process.Process
CPU float64
}

type NetworkStats struct {
InterfaceName string
BytesSent uint64
BytesRecv uint64
PacketsSent uint64
PacketsRecv uint64
}

func getLoadAverage(ctx context.Context) (*load.AvgStat, error) {
avg, err := load.AvgWithContext(ctx)
if err != nil {
return nil, err
}
return avg, nil
}

func getTotalCPUUsage(ctx context.Context) (float64, error) {
percentages, err := cpu.PercentWithContext(ctx, 0, false)
if err != nil {
return 0, err
}
if len(percentages) > 0 {
return percentages[0], nil
}
return 0, fmt.Errorf("could not get CPU usage")
}

func getCPUPercentages(ctx context.Context) ([]float64, error) {
cpuStats, err := cpu.PercentWithContext(ctx, 0, true)
if err != nil {
return nil, err
}
return cpuStats, nil
}

func getMemoryUsed(ctx context.Context) (*mem.VirtualMemoryStat, error) {
percentages, err := mem.VirtualMemoryWithContext(ctx)
if err != nil {
return nil, err
}
return percentages, nil
}

func getDiskUsage(ctx context.Context) (*disk.UsageStat, error) {
usage, err := disk.UsageWithContext(ctx, "/")
if err != nil {
return nil, err
}
return usage, nil
}

func getTopProcesses(ctx context.Context) ([]ProcessInfo, error) {
proces, err := process.Processes()
if err != nil {
return nil, err
}

var topProcesses []ProcessInfo

for _, p := range proces {
cpuPercent, _ := p.CPUPercent()
topProcesses = append(topProcesses, ProcessInfo{
Proc: p,
CPU: cpuPercent,
})
}

sort.Slice(topProcesses, func(i, j int) bool {
return topProcesses[i].CPU > topProcesses[j].CPU
})

if len(topProcesses) > 10 {
topProcesses = topProcesses[:10]
}
return topProcesses, nil
}

func getNetworkStatistics(ctx context.Context) ([]NetworkStats, error) {
ioCounters, err := net.IOCounters(true)
if err != nil {
return nil, err
}

var stats []NetworkStats
for _, counter := range ioCounters {
if counter.BytesSent > 0 && counter.BytesRecv > 0 && counter.PacketsSent > 0 && counter.PacketsRecv > 0 {
stats = append(stats, NetworkStats{
InterfaceName: counter.Name,
BytesSent: counter.BytesSent,
BytesRecv: counter.BytesRecv,
PacketsSent: counter.PacketsSent,
PacketsRecv: counter.PacketsRecv,
})
}
}
return stats, nil
}

func main() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

cpuStats, err := getCPUPercentages(ctx)
if err != nil {
fmt.Println("Error fetching CPU Statistics: ", err)
return
}
cpuWriter := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(cpuWriter, "CPU Core\tPercentage")
for i, percent := range cpuStats {
fmt.Fprintf(cpuWriter, " %d:\t%.2f%%\n", i, percent)
}

cpuWriter.Flush()

totalCPUUsage, err := getTotalCPUUsage(ctx)
if err != nil {
fmt.Println("Error getting total CPU usage:", err)
return
}
fmt.Printf("Total CPU usage: %f%%\n", totalCPUUsage)
fmt.Println("-----------")

loadAvg, err := getLoadAverage(ctx)
if err != nil {
fmt.Println("Error getting load average:", err)
return
}
fmt.Printf("Load average: 1 min: %.2f, 5 min: %.2f, 15 min: %.2f\n", loadAvg.Load1, loadAvg.Load5, loadAvg.Load15)

fmt.Println("-----------")
memoryPercentages, err := getMemoryUsed(ctx)
if err != nil {
fmt.Println("Error getting memory statistics:", err)
return
}
fmt.Printf("Memory Usage: %.2f%%\n", memoryPercentages.UsedPercent)
fmt.Println("-----------")

diskUsage, err := getDiskUsage(ctx)
if err != nil {
fmt.Println("Error getting disk usage:", err)
return
}
diskWriter := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(diskWriter, "DiskSpace\tFree Space\tTotal Usage\t")
fmt.Fprintf(diskWriter, "%.2f GB\t%.2f GB\t%.2f GB\t\n",
float64(diskUsage.Total)/float64(1<<30),
float64(diskUsage.Free)/float64(1<<30),
float64(diskUsage.Used)/float64(1<<30))
diskWriter.Flush()

//fmt.Printf("Disk Usage: \nTotal=%.2f GB \nFree=%.2f GB \nUsed=%.2f GB \nInodesFree=%d \nInodesUsed=%d\n", float64(diskUsage.Total)/float64(1<<30), float64(diskUsage.Free)/float64(1<<30), float64(diskUsage.Used)/float64(1<<30), diskUsage.InodesFree, diskUsage.InodesUsed)

topProcesses, err := getTopProcesses(ctx)
if err != nil {
fmt.Println("Error getting top processes:", err)
return
}
fmt.Println("-----------")
fmt.Println("Top 10 Processes by CPU usage:")
topCPUW := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)

fmt.Fprintln(topCPUW, "PID\tCPU(%)\tMemory(MB)\tName")

for _, p := range topProcesses {
memInfo, err := p.Proc.MemoryInfo()
if err != nil {
fmt.Println("Error getting memory info:", err)
continue
if err := ui.Init(); err != nil {
log.Fatalf("Failed to initialize termui: %v", err)
}
defer ui.Close()

v := view.NewView()
v.SetLayout()
v.ResetSize()

uiEvents := ui.PollEvents()
ticker := time.NewTicker(time.Second).C

updateFuncs := []func(context.Context, *view.View){
uiupdate.UpdateCPUData,
uiupdate.UpdateTopProcesses,
uiupdate.UpdateMemUsage,
uiupdate.UpdateDiskUsage,
uiupdate.UpdateNetworkStatistics,
uiupdate.UpdateLoadAverage,
uiupdate.UpdateTotalCPUUsage,
}

for {
select {
case e := <-uiEvents:
switch e.ID {
case "q", "c":
return
case "<Resize>":
v.ResetSize()
case "<Up>":
v.ProcessList.ScrollUp()
case "<Down>":
v.ProcessList.ScrollDown()
}
case <-ticker:
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
var wg sync.WaitGroup
for _, updateFunc := range updateFuncs {
wg.Add(1)
go func(f func(context.Context, *view.View)) {
defer wg.Done()
f(ctx, v)
}(updateFunc)
}
wg.Wait()
cancel()

v.Render()
}
name, err := p.Proc.Name()
if err != nil {
fmt.Println("Error getting process name:", err)
continue
}

// Use Fprintf to write formatted output to the tab writer
fmt.Fprintf(topCPUW, "%d\t%.2f\t%.2f\t%s\n",
p.Proc.Pid, p.CPU, float32(memInfo.RSS)/(1<<20), name)
}

topCPUW.Flush()

fmt.Println("-----------")
fmt.Println("Net Info")

netStats, err := getNetworkStatistics(ctx)
if err != nil {
fmt.Println("Error getting network stats:", err)
return
}
fmt.Println("-----------")
netWriter := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(netWriter, "Interface Name\tBytes Sent (Gbits)\tBytes Received (Gbits)\tPackets Sent\tPackets Received\t")
for _, netStat := range netStats {
fmt.Fprintf(netWriter, "%s\t%.2f\t%.2f\t%d\t%d\t\n",
netStat.InterfaceName,
float64(netStat.BytesSent)*8/(1<<30),
float64(netStat.BytesRecv)*8/(1<<30),
netStat.PacketsSent,
netStat.PacketsRecv)
}
netWriter.Flush()
}
Loading

0 comments on commit f9c7fc6

Please sign in to comment.