142 lines
3.3 KiB
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 {
|
|
Process string `json:"process"`
|
|
Alias string `json:"alias"`
|
|
}
|
|
|
|
// ProcessesState is a map of processes' aliases and its statuses.
|
|
type ProcessesState map[string]bool
|
|
|
|
func GetProcessesState(procs *[]Process) (ps ProcessesState) {
|
|
ps = make(ProcessesState, len(*procs))
|
|
|
|
for _, proc := range *procs {
|
|
run, _ := IsProcessRuns(proc.Process)
|
|
ps[proc.Alias] = run
|
|
}
|
|
|
|
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(log.Llongfile)
|
|
flag.Parse()
|
|
|
|
if *showVersion {
|
|
fmt.Fprintln(os.Stderr, "httpprocprobed ver.", version, "\nCopyright (c) 2021-2024 Alexander \"Arav\" Andreev <me@arav.su>")
|
|
return
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|