1
0
Fork 0
httpprocprobed/main.go

142 lines
3.3 KiB
Go

package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
var configPath *string = flag.String("c", "config.json", "path to configuration file")
var showVersion *bool = flag.Bool("v", false, "show version")
var version string
// Process contains an alias that will be returned when queried, and a process
// name to look for.
// A process is effectively a substring that is being looked in a cmdline file
// in /proc/ dir on unix-like systems.
type Process struct {
Alias string `json:"alias"`
Process string `json:"process"`
}
// ProcessesState is a map of processes' aliases and its statuses.
type ProcessesState map[string]bool
func GetProcessesState(procs *[]Process) (ps ProcessesState) {
ps = make(ProcessesState)
for _, proc := range *procs {
pids, err := GetProcessPIDs(proc.Process)
ps[proc.Alias] = err == nil && len(pids) > 0
}
return
}
type Configuration struct {
ListenAddress string `json:"listen-address"`
Processes []Process `json:"processes"`
}
func LoadConfiguration(path string) (conf *Configuration, err error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
conf = &Configuration{}
if err := json.NewDecoder(f).Decode(conf); err != nil {
return nil, err
}
for i := 0; i < len(conf.Processes); i++ {
if conf.Processes[i].Process == "" {
return nil, fmt.Errorf("an empty process field found")
}
if conf.Processes[i].Alias == "" {
conf.Processes[i].Alias = conf.Processes[i].Process
}
}
return conf, nil
}
func main() {
log.SetFlags(0)
flag.Parse()
if *showVersion {
fmt.Fprintln(os.Stderr, "httpprocprobed ver.", version, "\nCopyright (c) 2021-2024 Alexander \"Arav\" Andreev <me@arav.su>")
os.Exit(0)
}
conf, err := LoadConfiguration(*configPath)
if err != nil {
log.Fatalf("Cannot load configuration file: %s\n", err)
}
router := http.NewServeMux()
router.HandleFunc("/processes", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
w.WriteHeader(http.StatusMethodNotAllowed)
w.Header().Add("Allow", "GET")
return
}
w.Header().Add("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(GetProcessesState(&conf.Processes)); err != nil {
w.WriteHeader(http.StatusInternalServerError)
log.Printf("Failed to encode a process list: %s\n", err)
}
})
srv := &http.Server{
Addr: conf.ListenAddress,
Handler: router,
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
IdleTimeout: 10 * time.Second,
}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("ListenAndServe: %s\n", err)
}
}()
defer func() {
srv.SetKeepAlivesEnabled(false)
if err := srv.Shutdown(context.Background()); err != nil {
log.Fatalf("%s\n", err)
}
}()
syssignal := make(chan os.Signal, 1)
signal.Notify(syssignal, os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
for {
switch <-syssignal {
case os.Interrupt:
fallthrough
case syscall.SIGINT | syscall.SIGTERM:
return
case syscall.SIGHUP:
newconf, err := LoadConfiguration(*configPath)
if err != nil {
log.Fatalf("Failed to reload a list of processes from configuration: %s\n", err)
}
conf.Processes = newconf.Processes
}
}
}