From 59cc4061d91c35376a8bf5fc73496674202d89e6 Mon Sep 17 00:00:00 2001 From: martabal <74269598+martabal@users.noreply.github.com> Date: Wed, 17 Apr 2024 19:09:37 +0200 Subject: [PATCH] fix: perf --- src/api/qbittorrent.go | 2 +- src/app/app.go | 31 ++++++++++ src/go.mod | 2 +- src/go.sum | 4 +- src/logger/log.go | 29 ++++----- src/{init.go => main.go} | 45 ++++++++------ src/models/app.go | 39 ------------ src/models/qbit.go | 44 -------------- src/prometheus/prometheus.go | 3 +- src/prometheus/prometheus_test.go | 6 +- src/qbit/auth.go | 18 +++--- src/qbit/qbit.go | 98 +++++++++++++++---------------- 12 files changed, 132 insertions(+), 189 deletions(-) create mode 100644 src/app/app.go rename src/{init.go => main.go} (76%) delete mode 100644 src/models/app.go delete mode 100644 src/models/qbit.go diff --git a/src/api/qbittorrent.go b/src/api/qbittorrent.go index 878d14d..ba62dba 100644 --- a/src/api/qbittorrent.go +++ b/src/api/qbittorrent.go @@ -36,7 +36,7 @@ type Preferences struct { UpLimit int `json:"up_limit"` } -type Maindata struct { +type MainData struct { CategoryMap map[string]Category `json:"categories"` ServerState struct { AlltimeDl int `json:"alltime_dl"` diff --git a/src/app/app.go b/src/app/app.go new file mode 100644 index 0000000..04fe168 --- /dev/null +++ b/src/app/app.go @@ -0,0 +1,31 @@ +package app + +import "strings" + +var ( + Port int + ShouldShowError bool + DisableTracker bool + LogLevel string + BaseUrl string + Cookie string + Username string + Password string +) + +func SetApp(port int, disableTracker bool, loglevel string) { + Port = port + ShouldShowError = false + DisableTracker = disableTracker + LogLevel = loglevel +} + +func SetQbit(baseUrl string, username string, password string) { + BaseUrl = baseUrl + Username = username + Password = password +} + +func GetPasswordMasked() string { + return strings.Repeat("*", len(Password)) +} diff --git a/src/go.mod b/src/go.mod index 2fcc4f2..286ebb3 100644 --- a/src/go.mod +++ b/src/go.mod @@ -11,7 +11,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.52.2 // indirect + github.com/prometheus/common v0.52.3 // indirect github.com/prometheus/procfs v0.13.0 // indirect golang.org/x/sys v0.19.0 // indirect google.golang.org/protobuf v1.33.0 // indirect diff --git a/src/go.sum b/src/go.sum index e2131c3..c732aa4 100644 --- a/src/go.sum +++ b/src/go.sum @@ -12,8 +12,8 @@ github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7km github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.52.2 h1:LW8Vk7BccEdONfrJBDffQGRtpSzi5CQaRZGtboOO2ck= -github.com/prometheus/common v0.52.2/go.mod h1:lrWtQx+iDfn2mbH5GUzlH9TSHyfZpHkSiG1W7y3sF2Q= +github.com/prometheus/common v0.52.3 h1:5f8uj6ZwHSscOGNdIQg6OiZv/ybiK2CO2q2drVZAQSA= +github.com/prometheus/common v0.52.3/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U= github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o= github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= diff --git a/src/logger/log.go b/src/logger/log.go index 13cdf6c..7cd884a 100644 --- a/src/logger/log.go +++ b/src/logger/log.go @@ -8,10 +8,11 @@ import ( "os" ) -type Level int +type PrettyHandler struct { + slog.Handler +} -var LogLevels = map[string]Level{ - "TRACE": Trace, +var LogLevels = map[string]int{ "DEBUG": Debug, "INFO": Info, "WARN": Warn, @@ -19,11 +20,10 @@ var LogLevels = map[string]Level{ } const ( - Trace Level = -8 - Debug Level = -4 - Info Level = 0 - Warn Level = 4 - Error Level = 8 + Debug int = -4 + Info int = 0 + Warn int = 4 + Error int = 8 ) const ( @@ -32,17 +32,8 @@ const ( Green = "\033[32m" Yellow = "\033[33m" Blue = "\033[34m" - White = "\033[97m" ) -type PrettyHandlerOptions struct { - SlogOpts slog.HandlerOptions -} - -type PrettyHandler struct { - slog.Handler -} - func (h *PrettyHandler) Handle(ctx context.Context, r slog.Record) error { level := r.Level.String() timeStr := fmt.Sprintf("[%02d-%02d-%02d %02d:%02d:%02d]", r.Time.Year(), r.Time.Month(), r.Time.Day(), r.Time.Hour(), r.Time.Minute(), r.Time.Second()) @@ -67,10 +58,10 @@ func (h *PrettyHandler) Handle(ctx context.Context, r slog.Record) error { func NewPrettyHandler( out io.Writer, - opts PrettyHandlerOptions, + opts slog.HandlerOptions, ) *PrettyHandler { h := &PrettyHandler{ - Handler: slog.NewTextHandler(out, &opts.SlogOpts), + Handler: slog.NewTextHandler(out, &opts), } return h diff --git a/src/init.go b/src/main.go similarity index 76% rename from src/init.go rename to src/main.go index 283b16c..2eabf42 100644 --- a/src/init.go +++ b/src/main.go @@ -4,14 +4,16 @@ import ( "flag" "fmt" "log/slog" + "net" "net/http" "os" - "qbit-exp/models" - qbit "qbit-exp/qbit" - "strconv" - "strings" + "qbit-exp/qbit" + + app "qbit-exp/app" logger "qbit-exp/logger" + "strconv" + "strings" "github.com/joho/godotenv" "github.com/prometheus/client_golang/prometheus" @@ -30,18 +32,18 @@ func main() { loadenv() fmt.Printf("%s (version %s)\n", ProjectName, Version) fmt.Println("Author:", Author) - fmt.Println("Using log level: " + models.GetLogLevel()) + fmt.Println("Using log level: " + app.LogLevel) qbit.Auth(true) - logger.Log.Info("qbittorrent URL: " + models.Getbaseurl()) - logger.Log.Info("username: " + models.GetUsername()) - logger.Log.Info("password: " + models.Getpasswordmasked()) + logger.Log.Info("qbittorrent URL: " + app.BaseUrl) + logger.Log.Info("username: " + app.Username) + logger.Log.Info("password: " + app.GetPasswordMasked()) logger.Log.Info("Started") http.HandleFunc("/metrics", metrics) - addr := ":" + strconv.Itoa(models.GetPort()) - if models.GetPort() != DEFAULTPORT { - logger.Log.Info("Listening on port " + strconv.Itoa(models.GetPort())) + addr := ":" + strconv.Itoa(app.Port) + if app.Port != DEFAULTPORT { + logger.Log.Info("Listening on port " + strconv.Itoa(app.Port)) } err := http.ListenAndServe(addr, nil) if err != nil { @@ -50,9 +52,15 @@ func main() { } func metrics(w http.ResponseWriter, req *http.Request) { - logger.Log.Debug("New request") + ip, _, err := net.SplitHostPort(req.RemoteAddr) + if err == nil { + logger.Log.Debug("New request from " + ip) + } else { + logger.Log.Debug("New request from") + } + registry := prometheus.NewRegistry() - qbit.Allrequests(registry) + qbit.AllRequests(registry) h := promhttp.HandlerFor(registry, promhttp.HandlerOpts{}) h.ServeHTTP(w, req) } @@ -86,8 +94,8 @@ func loadenv() { panic("EXPORTER_PORT must be > 0 and < 65353") } - models.SetApp(num, false, strings.ToLower(disableTracker) == "true", loglevel) - models.SetQbit(qbitURL, qbitUsername, qbitPassword) + app.SetApp(num, strings.ToLower(disableTracker) == "true", loglevel) + app.SetQbit(qbitURL, qbitUsername, qbitPassword) } func setLogLevel(logLevel string) string { @@ -98,11 +106,10 @@ func setLogLevel(logLevel string) string { level = logger.LogLevels[upperLogLevel] } - opts := logger.PrettyHandlerOptions{ - SlogOpts: slog.HandlerOptions{ - Level: slog.Level(level), - }, + opts := slog.HandlerOptions{ + Level: slog.Level(level), } + handler := logger.NewPrettyHandler(os.Stdout, opts) logger.Log = slog.New(handler) return upperLogLevel diff --git a/src/models/app.go b/src/models/app.go deleted file mode 100644 index db648e4..0000000 --- a/src/models/app.go +++ /dev/null @@ -1,39 +0,0 @@ -package models - -type TypeAppConfig struct { - Port int - Error bool - LogLevel string - DisableTracker bool -} - -var AppConfig TypeAppConfig - -func SetApp(setport int, seterror bool, setdisableTracker bool, setloglevel string) { - AppConfig = TypeAppConfig{ - Port: setport, - Error: seterror, - DisableTracker: setdisableTracker, - LogLevel: setloglevel, - } -} - -func GetFeatureFlag() bool { - return AppConfig.DisableTracker -} - -func GetPort() int { - return AppConfig.Port -} - -func SetPromptError(prompt bool) { - AppConfig.Error = prompt -} - -func GetLogLevel() string { - return AppConfig.LogLevel -} - -func GetPromptError() bool { - return AppConfig.Error -} diff --git a/src/models/qbit.go b/src/models/qbit.go deleted file mode 100644 index 44d3a39..0000000 --- a/src/models/qbit.go +++ /dev/null @@ -1,44 +0,0 @@ -package models - -import "strings" - -type TypeQbitConfig struct { - Base_url string - Cookie string - Username string - Password string -} - -var Config TypeQbitConfig - -func SetQbit(baseurl string, username string, password string) { - Config = TypeQbitConfig{ - Base_url: baseurl, - Username: username, - Password: password, - } -} - -func Setcookie(cookie string) { - Config.Cookie = cookie -} - -func Getbaseurl() string { - return Config.Base_url -} - -func Getcookie() string { - return Config.Cookie -} - -func GetUsername() string { - return Config.Username -} - -func Getpassword() string { - return Config.Password -} - -func Getpasswordmasked() string { - return strings.Repeat("*", len(Config.Password)) -} diff --git a/src/prometheus/prometheus.go b/src/prometheus/prometheus.go index 619c1a8..6a97e1d 100644 --- a/src/prometheus/prometheus.go +++ b/src/prometheus/prometheus.go @@ -30,7 +30,6 @@ func isValidURL(input string) bool { } func Torrent(result *API.Info, r *prometheus.Registry) { - qbittorrent_eta := prometheus.NewGaugeVec(prometheus.GaugeOpts{ Name: "qbittorrent_torrent_eta", Help: "The current ETA for each torrent (in seconds)", @@ -250,7 +249,7 @@ func Trackers(result []*API.Trackers, r *prometheus.Registry) { } -func MainData(result *API.Maindata, r *prometheus.Registry) { +func MainData(result *API.MainData, r *prometheus.Registry) { globalratio, err := strconv.ParseFloat((*result).ServerState.GlobalRatio, 64) if err != nil { diff --git a/src/prometheus/prometheus_test.go b/src/prometheus/prometheus_test.go index 46efd76..8e16bd7 100644 --- a/src/prometheus/prometheus_test.go +++ b/src/prometheus/prometheus_test.go @@ -1,13 +1,13 @@ package prom import ( - "qbit-exp/models" + app "qbit-exp/app" "testing" ) func TestMain(t *testing.T) { - models.SetQbit("http://localhost:8080", "admin", "adminadmin") - result := models.Getpasswordmasked() + app.SetQbit("http://localhost:8080", "admin", "adminadmin") + result := app.GetPasswordMasked() if !isValidMaskedPassword(result) { t.Errorf("Invalid masked password. Expected only asterisks, got: %s", result) diff --git a/src/qbit/auth.go b/src/qbit/auth.go index bc30cfc..a542d5f 100644 --- a/src/qbit/auth.go +++ b/src/qbit/auth.go @@ -4,22 +4,22 @@ import ( "io" "net/http" "net/url" + app "qbit-exp/app" "qbit-exp/logger" - "qbit-exp/models" "strconv" "strings" ) func Auth(init bool) { params := url.Values{ - "username": {models.GetUsername()}, - "password": {models.Getpassword()}, + "username": {app.Username}, + "password": {app.Password}, } - resp, err := http.PostForm(models.Getbaseurl()+"/api/v2/auth/login", params) + resp, err := http.PostForm(app.BaseUrl+"/api/v2/auth/login", params) if err != nil { - if !models.GetPromptError() { - models.SetPromptError(true) - logger.Log.Warn("Can't connect to qbittorrent with url : " + models.Getbaseurl()) + if !app.ShouldShowError { + app.ShouldShowError = true + logger.Log.Warn("Can't connect to qbittorrent with url : " + app.BaseUrl) } return } @@ -41,12 +41,12 @@ func Auth(init bool) { panic("Authentication Error, check your qBittorrent username / password") } - if models.GetPromptError() { + if app.ShouldShowError { logger.Log.Info("New cookie stored") } cookie := resp.Header.Get("Set-Cookie") cookieValue := strings.Split(strings.Split(cookie, ";")[0], "=")[1] - models.Setcookie(cookieValue) + app.Cookie = cookieValue } diff --git a/src/qbit/qbit.go b/src/qbit/qbit.go index 4fa2abe..a89a581 100644 --- a/src/qbit/qbit.go +++ b/src/qbit/qbit.go @@ -9,18 +9,14 @@ import ( "net/http" API "qbit-exp/api" + "qbit-exp/app" "qbit-exp/logger" - "qbit-exp/models" + prom "qbit-exp/prometheus" "github.com/prometheus/client_golang/prometheus" ) -var ( - wg sync.WaitGroup - wgTracker sync.WaitGroup -) - type QueryParams struct { Key string Value string @@ -33,8 +29,18 @@ type Data struct { QueryParams *[]QueryParams } +type UniqueTracker struct { + Tracker string + Hash string +} + const UnmarshError = "Can not unmarshal JSON for " +var ( + wg sync.WaitGroup + wgTracker sync.WaitGroup +) + var info = []Data{ { URL: "/api/v2/app/version", @@ -89,31 +95,25 @@ func getData(r *prometheus.Registry, data Data, goroutine bool) bool { case "info": result := new(API.Info) if err := json.Unmarshal(body, &result); err != nil { - logger.Log.Debug(string(body)) - logger.Log.Debug(err.Error()) - logger.Log.Error(unmarshErr) + errorHelper(body, err, unmarshErr) } else { prom.Torrent(result, r) - if !models.GetFeatureFlag() { + if !app.DisableTracker { getTrackers(result, r) } } case "maindata": - result := new(API.Maindata) + result := new(API.MainData) if err := json.Unmarshal(body, &result); err != nil { - logger.Log.Debug(string(body)) - logger.Log.Debug(err.Error()) - logger.Log.Error(unmarshErr) + errorHelper(body, err, unmarshErr) } else { prom.MainData(result, r) } case "preference": result := new(API.Preferences) if err := json.Unmarshal(body, &result); err != nil { - logger.Log.Debug(string(body)) - logger.Log.Debug(err.Error()) - logger.Log.Error(unmarshErr) + errorHelper(body, err, unmarshErr) } else { prom.Preference(result, r) } @@ -130,9 +130,7 @@ func getData(r *prometheus.Registry, data Data, goroutine bool) bool { case "transfer": result := new(API.Transfer) if err := json.Unmarshal(body, &result); err != nil { - logger.Log.Debug(string(body)) - logger.Log.Debug(err.Error()) - logger.Log.Error(unmarshErr) + errorHelper(body, err, unmarshErr) } else { prom.Transfer(result, r) } @@ -158,35 +156,26 @@ func getTrackersInfo(data Data, c chan func() (*API.Trackers, error)) { result := new(API.Trackers) unmarshErr := UnmarshError + "tracker" if err := json.Unmarshal(body, &result); err != nil { - logger.Log.Debug(string(body)) - logger.Log.Debug(err.Error()) - logger.Log.Error(unmarshErr) + errorHelper(body, err, unmarshErr) } else { c <- (func() (*API.Trackers, error) { return result, err }) } } -type UniqueObject struct { - Tracker string - Hash string -} - func getTrackers(torrentList *API.Info, r *prometheus.Registry) { uniqueValues := make(map[string]struct{}) - var uniqueObjects []UniqueObject + var uniqueTrackers []UniqueTracker for _, obj := range *torrentList { - if _, exists := uniqueValues[obj.Tracker]; !exists { - uniqueValues[obj.Tracker] = struct{}{} - uniqueObjects = append(uniqueObjects, UniqueObject{Tracker: obj.Tracker, Hash: obj.Hash}) + uniqueTrackers = append(uniqueTrackers, UniqueTracker{Tracker: obj.Tracker, Hash: obj.Hash}) } } responses := new([]*API.Trackers) - for i := 1; i < len(uniqueObjects); i++ { - tracker := make(chan func() (*API.Trackers, error)) + tracker := make(chan func() (*API.Trackers, error)) + for i := 0; i < len(uniqueTrackers); i++ { var trackerInfo = Data{ URL: "/api/v2/torrents/trackers", HTTPMethod: http.MethodGet, @@ -194,25 +183,29 @@ func getTrackers(torrentList *API.Info, r *prometheus.Registry) { QueryParams: &[]QueryParams{ { Key: "hash", - Value: uniqueObjects[i].Hash, + Value: uniqueTrackers[i].Hash, }, }, } wgTracker.Add(1) go getTrackersInfo(trackerInfo, tracker) - res, err := (<-tracker)() + } + go func() { + wgTracker.Wait() + close(tracker) + }() + for respFunc := range tracker { + res, err := respFunc() if err == nil { *responses = append(*responses, res) } } - wgTracker.Wait() prom.Trackers(*responses, r) - } -func Allrequests(r *prometheus.Registry) { +func AllRequests(r *prometheus.Registry) { retry := getData(r, info[0], false) if retry { logger.Log.Debug("Retrying ...") @@ -225,9 +218,14 @@ func Allrequests(r *prometheus.Registry) { wg.Wait() } -func apiRequest(uri string, method string, queryParams *[]QueryParams) (*http.Response, bool, error) { +func errorHelper(body []byte, err error, unmarshErr string) { + logger.Log.Debug(string(body)) + logger.Log.Debug(err.Error()) + logger.Log.Error(unmarshErr) +} - req, err := http.NewRequest(method, models.Getbaseurl()+uri, nil) +func apiRequest(uri string, method string, queryParams *[]QueryParams) (*http.Response, bool, error) { + req, err := http.NewRequest(method, app.BaseUrl+uri, nil) if err != nil { panic("Error with url") } @@ -239,36 +237,36 @@ func apiRequest(uri string, method string, queryParams *[]QueryParams) (*http.Re req.URL.RawQuery = q.Encode() } - req.AddCookie(&http.Cookie{Name: "SID", Value: models.Getcookie()}) + req.AddCookie(&http.Cookie{Name: "SID", Value: app.Cookie}) client := &http.Client{} resp, err := client.Do(req) if err != nil { err := fmt.Errorf("can't connect to server") - if !models.GetPromptError() { + if !app.ShouldShowError { logger.Log.Debug(err.Error()) - models.SetPromptError(true) + app.ShouldShowError = true } return resp, false, err } switch resp.StatusCode { case http.StatusOK: - if models.GetPromptError() { - models.SetPromptError(false) + if app.ShouldShowError { + app.ShouldShowError = false } return resp, false, nil case http.StatusForbidden: err := fmt.Errorf("%d", resp.StatusCode) - if !models.GetPromptError() { - models.SetPromptError(true) + if !app.ShouldShowError { + app.ShouldShowError = true logger.Log.Warn("Cookie changed, try to reconnect ...") } Auth(false) return resp, true, err default: err := fmt.Errorf("%d", resp.StatusCode) - if !models.GetPromptError() { - models.SetPromptError(true) + if !app.ShouldShowError { + app.ShouldShowError = true logger.Log.Debug("Error code " + strconv.Itoa(resp.StatusCode)) } return resp, false, err