From 206ee48cfb984db5ea9ae983dba47243ebbc7139 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Thu, 19 Dec 2024 11:52:28 +0530 Subject: [PATCH 1/3] feat: initial commit --- .env | 5 + .github/workflows/docker-publish.yml | 26 +++ Dockerfile | 33 +++ LICENSE | 21 ++ README.md | 151 +++++++++++++ docker-compose.yml | 19 ++ go.mod | 16 ++ go.sum | 45 ++++ main.go | 326 +++++++++++++++++++++++++++ 9 files changed, 642 insertions(+) create mode 100644 .env create mode 100644 .github/workflows/docker-publish.yml create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.env b/.env new file mode 100644 index 0000000..bb104b6 --- /dev/null +++ b/.env @@ -0,0 +1,5 @@ +BETTER_STACK_URL=https://uptime.betterstack.com/api/v1/incoming-webhook/HYqP4eAdUr1q9WFQLdMvetUj +CHECK_INTERVAL=10 +CPU_LIMIT=90 +MEMORY_LIMIT=90 +DISK_LIMIT=85 diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 0000000..b5e7726 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,26 @@ +name: Docker Build and Publish + +on: + release: + types: [published] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout the repo + uses: actions/checkout@v4 + + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ghcr.io/appwrite/monitoring:${{ env.TAG }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a5f7878 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,33 @@ +FROM golang:1.21-alpine AS builder + +WORKDIR /app + +# Install build dependencies +RUN apk add --no-cache gcc musl-dev + +# Copy go mod files +COPY go.mod go.sum ./ + +# Download dependencies +RUN go mod download + +# Copy source code +COPY . . + +# Build the application +RUN CGO_ENABLED=1 GOOS=linux go build -a -o monitoring . + +# Copy the monitoring binary to /usr/local/bin to make it available in PATH +COPY monitoring /usr/local/bin/monitoring +RUN chmod +x /usr/local/bin/monitoring + +# Final stage +FROM alpine:3.19 + +WORKDIR /app + +# Install runtime dependencies +RUN apk add --no-cache ca-certificates + +# Copy binary from builder +COPY --from=builder /app/monitoring . \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..57db82d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Appwrite + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2251b9d --- /dev/null +++ b/README.md @@ -0,0 +1,151 @@ +# System Monitoring + +A lightweight system monitoring tool that tracks CPU, memory, and disk usage across your infrastructure. When resource usage exceeds defined thresholds, it creates incidents in BetterStack (formerly BetterUptime). + +## Features + +- CPU usage monitoring +- Memory usage monitoring +- Disk usage monitoring (root and mounted volumes) +- Automatic incident creation and resolution +- Configurable thresholds via CLI +- Docker-based deployment + +## Command Line Usage + +The monitoring tool is configured through command-line flags: + +```bash +monitoring [flags] + +Flags: + -url string + BetterStack webhook URL (required) + -interval int + Check interval in seconds (default: 300) + -cpu-limit float + CPU usage threshold percentage (default: 90) + -memory-limit float + Memory usage threshold percentage (default: 90) + -disk-limit float + Disk usage threshold percentage (default: 85) + -help + Display help information +``` + +### Examples + +```bash +# Basic usage with required URL +monitoring --url=https://betterstack.com/webhook/xyz + +# Custom thresholds +monitoring --url=https://betterstack.com/webhook/xyz \ + --cpu-limit=95 \ + --memory-limit=85 \ + --disk-limit=80 + +# More frequent checks (every minute) +monitoring --url=https://betterstack.com/webhook/xyz --interval=60 +``` + +## Docker Deployment + +### Using Docker Run + +```bash +docker run -d \ + --name monitoring \ + --privileged \ + --pid=host \ + -v /:/host:ro \ + ghcr.io/appwrite/monitoring:latest \ + monitoring \ + --url=https://betterstack.com/webhook/xyz \ + --interval=300 \ + --cpu-limit=90 \ + --memory-limit=90 \ + --disk-limit=85 +``` + +### Using Docker Compose + +The docker-compose.yml file is configured with default parameters that you can modify as needed: + +```bash +docker-compose up -d +``` + +To modify the parameters, edit the command section in docker-compose.yml: +```yaml +command: + - monitoring + - "--url=https://betterstack.com/webhook/xyz" + - "--interval=10" + - "--cpu-limit=90" + - "--memory-limit=80" + - "--disk-limit=85" +``` + +## Building from Source + +1. Clone the repository: +```bash +git clone https://github.com/appwrite/monitoring.git +cd monitoring +``` + +2. Build the binary: +```bash +go build -o monitoring +``` + +3. Run the monitoring tool: +```bash +monitoring --url=https://betterstack.com/webhook/xyz +``` + +## Development + +### Requirements +- Go 1.21 or later +- Docker and Docker Compose (for containerized deployment) + +### Local Development +1. Install dependencies: +```bash +go mod download +``` + +2. Build and run: +```bash +go build -o monitoring +monitoring --url=https://betterstack.com/webhook/xyz +``` + +### Docker Development +1. Build the image: +```bash +docker build -t monitoring . +``` + +2. Run with Docker: +```bash +# Show help +docker run --rm ghcr.io/appwrite/monitoring:latest monitoring --help + +# Run monitoring with custom parameters +docker run -d \ + --name monitoring \ + --privileged \ + --pid=host \ + -v /:/host:ro \ + ghcr.io/appwrite/monitoring:latest \ + monitoring \ + --url=https://betterstack.com/webhook/xyz \ + --interval=60 +``` + +## License + +MIT License - see the [LICENSE](LICENSE) file for details \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..fd91496 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,19 @@ +version: '3.8' + +services: + monitoring: + build: + context: . + dockerfile: Dockerfile + command: + - monitoring + - "--url=${BETTER_STACK_URL}" + - "--interval=10" + - "--cpu-limit=90" + - "--memory-limit=80" + - "--disk-limit=85" + volumes: + - /:/host:ro + pid: host + privileged: true + restart: unless-stopped \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f881372 --- /dev/null +++ b/go.mod @@ -0,0 +1,16 @@ +module github.com/appwrite/monitoring + +go 1.19 + +require github.com/shirou/gopsutil/v3 v3.24.1 + +require ( + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect + golang.org/x/sys v0.16.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..ad5810c --- /dev/null +++ b/go.sum @@ -0,0 +1,45 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/shirou/gopsutil/v3 v3.24.1 h1:R3t6ondCEvmARp3wxODhXMTLC/klMa87h2PHUw5m7QI= +github.com/shirou/gopsutil/v3 v3.24.1/go.mod h1:UU7a2MSBQa+kW1uuDq8DeEBS8kmrnQwsv2b5O513rwU= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..4d63b86 --- /dev/null +++ b/main.go @@ -0,0 +1,326 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "log" + "net/http" + "os" + "path/filepath" + "strings" + "time" + + "github.com/shirou/gopsutil/v3/cpu" + "github.com/shirou/gopsutil/v3/disk" + "github.com/shirou/gopsutil/v3/mem" +) + +type Incident struct { + Title string `json:"title"` + Cause string `json:"cause"` + AlertID string `json:"alert_id"` + Timestamp int64 `json:"timestamp"` + Resolved bool `json:"resolved,omitempty"` +} + +type SystemMonitor struct { + httpClient *http.Client + incidents map[string][]Incident + betterStackURL string + hostname string + cpuLimit float64 + memoryLimit float64 + diskLimit float64 + interval int +} + +func NewSystemMonitor(betterStackURL string, interval int, cpuLimit, memoryLimit, diskLimit float64) (*SystemMonitor, error) { + hostname, err := os.Hostname() + if err != nil { + return nil, fmt.Errorf("failed to get hostname: %v", err) + } + + return &SystemMonitor{ + httpClient: &http.Client{ + Timeout: 5 * time.Second, + }, + incidents: map[string][]Incident{ + "cpu": {}, + "memory": {}, + "disk": {}, + }, + betterStackURL: betterStackURL, + hostname: hostname, + cpuLimit: cpuLimit, + memoryLimit: memoryLimit, + diskLimit: diskLimit, + interval: interval, + }, nil +} + +func (s *SystemMonitor) evaluateCPUIncident() (*Incident, error) { + duration := float64(s.interval) / 10 + if duration < 5 { + duration = 5 + } + if duration > 60 { + duration = 60 + } + + cpuPercent, err := cpu.Percent(time.Duration(duration)*time.Second, false) + if err != nil { + return nil, fmt.Errorf("failed to get CPU usage: %v", err) + } + + if len(cpuPercent) == 0 { + return nil, nil + } + + log.Printf("CPU usage: %.2f%%\n", cpuPercent[0]) + if cpuPercent[0] > s.cpuLimit { + return &Incident{ + Title: fmt.Sprintf("CPU usage higher than %.0f%%! - %s", s.cpuLimit, s.hostname), + Cause: "High CPU usage", + AlertID: fmt.Sprintf("high-cpu-%s", s.hostname), + Timestamp: time.Now().Unix(), + }, nil + } + + return nil, nil +} + +func (s *SystemMonitor) evaluateMemoryIncident() (*Incident, error) { + vmStat, err := mem.VirtualMemory() + if err != nil { + return nil, fmt.Errorf("failed to get memory stats: %v", err) + } + + log.Printf("Memory usage: %.2f%% (Available: %d MB, Total: %d MB)\n", + vmStat.UsedPercent, + vmStat.Available/(1024*1024), + vmStat.Total/(1024*1024)) + + if vmStat.UsedPercent > s.memoryLimit { + return &Incident{ + Title: fmt.Sprintf("Memory usage higher than %.0f%%! - %s", s.memoryLimit, s.hostname), + Cause: "High memory usage", + AlertID: fmt.Sprintf("high-memory-%s", s.hostname), + Timestamp: time.Now().Unix(), + }, nil + } + + return nil, nil +} + +func (s *SystemMonitor) evaluateDiskIncident() ([]Incident, error) { + var incidents []Incident + + // Check root partition + usage, err := disk.Usage("/") + if err != nil { + return nil, fmt.Errorf("failed to get disk usage: %v", err) + } + + log.Printf("Diskspace used /: %.2f%% (Free: %d MB, Total: %d MB)\n", + usage.UsedPercent, + usage.Free/(1024*1024), + usage.Total/(1024*1024)) + + if usage.UsedPercent > s.diskLimit { + incidents = append(incidents, Incident{ + Title: fmt.Sprintf("Root disk usage higher than %.0f%%! - %s", s.diskLimit, s.hostname), + Cause: "High disk usage", + AlertID: fmt.Sprintf("high-disk-%s", s.hostname), + Timestamp: time.Now().Unix(), + }) + } + + // Check mounted directories + mounts, err := filepath.Glob("/mnt/*") + if err != nil { + return nil, fmt.Errorf("failed to list mounted directories: %v", err) + } + + for _, mount := range mounts { + usage, err := disk.Usage(mount) + if err != nil { + log.Printf("Failed to get disk usage for %s: %v\n", mount, err) + continue + } + + log.Printf("Diskspace used %s: %.2f%% (Free: %d MB, Total: %d MB)\n", + mount, + usage.UsedPercent, + usage.Free/(1024*1024), + usage.Total/(1024*1024)) + + if usage.UsedPercent > s.diskLimit { + incidents = append(incidents, Incident{ + Title: fmt.Sprintf("%s disk usage higher than %.0f%%! - %s", mount, s.diskLimit, s.hostname), + Cause: "High disk usage", + AlertID: fmt.Sprintf("high-disk-%s", s.hostname), + Timestamp: time.Now().Unix(), + }) + } + } + + return incidents, nil +} + +func (s *SystemMonitor) createIncident(incident Incident) error { + log.Printf("Triggering incident: %s\n", incident.Title) + return s.sendIncident(incident) +} + +func (s *SystemMonitor) resolveIncident(incident Incident) error { + log.Printf("Resolving incident: %s\n", incident.Title) + incident.Resolved = true + return s.sendIncident(incident) +} + +func (s *SystemMonitor) sendIncident(incident Incident) error { + body, err := json.Marshal(incident) + if err != nil { + return fmt.Errorf("failed to marshal incident: %v", err) + } + + req, err := http.NewRequest(http.MethodPost, s.betterStackURL, strings.NewReader(string(body))) + if err != nil { + return fmt.Errorf("failed to create request: %v", err) + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("User-Agent", "Appwrite system-monitoring") + + resp, err := s.httpClient.Do(req) + if err != nil { + return fmt.Errorf("failed to send request: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode >= 400 { + return fmt.Errorf("request failed with status: %d", resp.StatusCode) + } + + return nil +} + +func (s *SystemMonitor) processType(monitorType string, evaluate func() (interface{}, error)) error { + incidents, err := evaluate() + if err != nil { + return fmt.Errorf("failed to evaluate %s: %v", monitorType, err) + } + + if incidents == nil { + if len(s.incidents[monitorType]) > 0 { + log.Printf("Resolving active incident of type %s\n", monitorType) + for _, incident := range s.incidents[monitorType] { + if err := s.resolveIncident(incident); err != nil { + log.Printf("Failed to resolve incident: %v\n", err) + } + } + s.incidents[monitorType] = nil + } + return nil + } + + if len(s.incidents[monitorType]) > 0 { + log.Printf("Already have active incident of type '%s', skipping.\n", monitorType) + return nil + } + + switch i := incidents.(type) { + case *Incident: + if i != nil { + if err := s.createIncident(*i); err != nil { + return fmt.Errorf("failed to create incident: %v", err) + } + s.incidents[monitorType] = []Incident{*i} + } + case []Incident: + for _, incident := range i { + if err := s.createIncident(incident); err != nil { + return fmt.Errorf("failed to create incident: %v", err) + } + } + s.incidents[monitorType] = i + } + + return nil +} + +func (s *SystemMonitor) Start() { + ticker := time.NewTicker(time.Duration(s.interval) * time.Second) + defer ticker.Stop() + + for range ticker.C { + if err := s.processType("cpu", func() (interface{}, error) { + return s.evaluateCPUIncident() + }); err != nil { + log.Printf("Error processing CPU metrics: %v\n", err) + } + + if err := s.processType("memory", func() (interface{}, error) { + return s.evaluateMemoryIncident() + }); err != nil { + log.Printf("Error processing memory metrics: %v\n", err) + } + + if err := s.processType("disk", func() (interface{}, error) { + return s.evaluateDiskIncident() + }); err != nil { + log.Printf("Error processing disk metrics: %v\n", err) + } + } +} + +func main() { + // Define command line flags + betterStackURL := flag.String("url", "", "BetterStack webhook URL (required)") + interval := flag.Int("interval", 300, "Check interval in seconds (default: 300)") + cpuLimit := flag.Float64("cpu-limit", 90.0, "CPU usage threshold percentage (default: 90)") + memoryLimit := flag.Float64("memory-limit", 90.0, "Memory usage threshold percentage (default: 90)") + diskLimit := flag.Float64("disk-limit", 85.0, "Disk usage threshold percentage (default: 85)") + + // Add usage message + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "Usage: %s [options]\n\nOptions:\n", os.Args[0]) + flag.PrintDefaults() + } + + flag.Parse() + + // Validate required flags + if *betterStackURL == "" { + flag.Usage() + log.Fatal("Error: BetterStack webhook URL is required") + } + + // Validate ranges + if *interval <= 0 { + log.Fatal("Error: interval must be greater than 0") + } + if *cpuLimit < 0 || *cpuLimit > 100 { + log.Fatal("Error: cpu-limit must be between 0 and 100") + } + if *memoryLimit < 0 || *memoryLimit > 100 { + log.Fatal("Error: memory-limit must be between 0 and 100") + } + if *diskLimit < 0 || *diskLimit > 100 { + log.Fatal("Error: disk-limit must be between 0 and 100") + } + + monitor, err := NewSystemMonitor(*betterStackURL, *interval, *cpuLimit, *memoryLimit, *diskLimit) + if err != nil { + log.Fatalf("Failed to create system monitor: %v", err) + } + + log.Printf("Starting monitoring with settings:") + log.Printf("- Check interval: %d seconds", *interval) + log.Printf("- CPU limit: %.1f%%", *cpuLimit) + log.Printf("- Memory limit: %.1f%%", *memoryLimit) + log.Printf("- Disk limit: %.1f%%", *diskLimit) + + monitor.Start() +} \ No newline at end of file From 9c34dff6de55c333f855bf797f4144d50071b4cc Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Thu, 19 Dec 2024 11:52:53 +0530 Subject: [PATCH 2/3] feat: initial commit --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index bb104b6..2dbf72f 100644 --- a/.env +++ b/.env @@ -1,4 +1,4 @@ -BETTER_STACK_URL=https://uptime.betterstack.com/api/v1/incoming-webhook/HYqP4eAdUr1q9WFQLdMvetUj +BETTER_STACK_URL= CHECK_INTERVAL=10 CPU_LIMIT=90 MEMORY_LIMIT=90 From 072e3931f15f1fcfbcd96c116ab7d362e9407d00 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Thu, 19 Dec 2024 11:58:13 +0530 Subject: [PATCH 3/3] feat: update readme --- README.md | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/README.md b/README.md index f5fe73e..8386f0e 100644 --- a/README.md +++ b/README.md @@ -124,26 +124,8 @@ monitoring --url=https://betterstack.com/webhook/xyz ``` ### Docker Development -1. Build the image: -```bash -docker build -t monitoring . ``` - -2. Run with Docker: -```bash -# Show help -docker run --rm ghcr.io/appwrite/monitoring:latest monitoring --help - -# Run monitoring with custom parameters -docker run -d \ - --name monitoring \ - --privileged \ - --pid=host \ - -v /:/host:ro \ - ghcr.io/appwrite/monitoring:latest \ - monitoring \ - --url=https://betterstack.com/webhook/xyz \ - --interval=60 +docker compose up -d ``` ## License