diff --git a/cli/commands.go b/cli/commands.go index e151b78a..3f53fed4 100644 --- a/cli/commands.go +++ b/cli/commands.go @@ -106,6 +106,7 @@ func (c *cli) CmdHelp() error { {"geoupdate", "Update geolocation of a mirror"}, {"list", "List all mirrors"}, {"logs", "Print logs of a mirror"}, + {"metrics", "Manage metrics"}, {"refresh", "Refresh the local repository"}, {"reload", "Reload configuration"}, {"remove", "Remove a mirror"}, @@ -115,7 +116,7 @@ func (c *cli) CmdHelp() error { {"upgrade", "Seamless binary upgrade"}, {"version", "Print version information"}, } { - help += fmt.Sprintf(" %-10.10s%s\n", command[0], command[1]) + help += fmt.Sprintf(" %-15.15s%s\n", command[0], command[1]) } fmt.Fprintf(os.Stderr, "%s\n", help) return nil @@ -350,6 +351,156 @@ func (c *cli) CmdAdd(args ...string) error { return nil } +func (c *cli) CmdMetrics(args ...string) error { + cmd := SubCmd("metrics", "COMMAND [OPTIONNAL ARGUMENTS]", "Manage metrics") + _ = cmd.String("add", "", "Add a file to the metrics route") + _ = cmd.String("list", "*", "List files in metrics + Optionnal pattern to filter results") + _ = cmd.String("delete", "", "Delete a file from the metrics route") + _ = cmd.Bool("status", false, "Show if metrics are enabled") + _ = cmd.Bool("auto-enable", true, "Enable automatic addition of new files to tracked files") + _ = cmd.Bool("auto-disable", false, "Disable automatic addition of new files to tracked files") + _ = cmd.Bool("auto-status", true, "Print boolean of automatic addition of new files to tracked files") + + // Can't use cmd.Parse(args) because it doesn't handle -command without + // an argument following which is needed for list + if len(args) < 1 || len(args) > 2 { + cmd.Usage() + return nil + } + if args[0] == "-list" { + pattern := "" + if len(args) == 2 { + pattern = args[1] + } + c.CmdListmetrics(pattern) + } else if args[0] == "-add" && len(args) == 2 { + c.CmdAddmetric(args[1]) + } else if args[0] == "-delete" && len(args) == 2 { + c.CmdDelmetric(args[1]) + } else if args[0] == "-auto-enable" { + c.CmdEnableauto() + } else if args[0] == "-auto-disable" { + c.CmdDisableauto() + } else if args[0] == "-auto-status" { + c.CmdStatusauto() + } else if args[0] == "-status" { + c.CmdStatusmetrics() + } else { + cmd.Usage() + return nil + } + return nil +} + +func (c *cli) CmdAddmetric(file string) error { + client := c.GetRPC() + ctx, cancel := context.WithTimeout(context.Background(), defaultRPCTimeout) + defer cancel() + _, err := client.AddMetric(ctx, &rpc.Metric{ + Filename: string(file), + }) + if err != nil { + log.Fatal("Error while adding metric: ", err) + } + + fmt.Printf("File %s successfully added to metrics.\n", file) + return nil +} + +func (c *cli) CmdDelmetric(file string) error { + client := c.GetRPC() + ctx, cancel := context.WithTimeout(context.Background(), defaultRPCTimeout) + defer cancel() + _, err := client.DelMetric(ctx, &rpc.Metric{ + Filename: string(file), + }) + if err != nil { + log.Fatal("Error while deleting metric: ", err) + } + + fmt.Printf("File %s successfully deleted from metrics.\n", file) + return nil +} + +func (c *cli) CmdListmetrics(pattern string) error { + filterPattern := "*" + if pattern != "" { + filterPattern = "*" + pattern + "*" + } + + client := c.GetRPC() + ctx, cancel := context.WithTimeout(context.Background(), defaultRPCTimeout) + defer cancel() + fileList, err := client.ListMetrics(ctx, &rpc.Metric{ + Filename: string(filterPattern), + }) + if err != nil { + log.Fatal("Error while listing metrics: ", err) + } + + w := new(tabwriter.Writer) + w.Init(os.Stdout, 0, 8, 0, '\t', 0) + if len(fileList.Filename) == 0 { + if pattern != "" { + fmt.Println("There are no tracked files matching your request.") + } else { + fmt.Println("There are no tracked files.") + } + } else { + for _, file := range fileList.Filename { + fmt.Println(file) + } + } + + return nil +} + +func (c *cli) CmdStatusmetrics() error { + client := c.GetRPC() + ctx, cancel := context.WithTimeout(context.Background(), defaultRPCTimeout) + defer cancel() + status, _ := client.GetStatusMetrics(ctx, &empty.Empty{}) + + if status.Status { + log.Info("Metrics are enabled") + } else { + log.Info("Metrics are disabled") + } + + return nil +} + +func (c *cli) CmdEnableauto() error { + client := c.GetRPC() + ctx, cancel := context.WithTimeout(context.Background(), defaultRPCTimeout) + defer cancel() + client.EnableAuto(ctx, &empty.Empty{}) + return nil +} + +func (c *cli) CmdDisableauto() error { + client := c.GetRPC() + ctx, cancel := context.WithTimeout(context.Background(), defaultRPCTimeout) + defer cancel() + client.DisableAuto(ctx, &empty.Empty{}) + return nil +} + +func (c *cli) CmdStatusauto() error { + client := c.GetRPC() + ctx, cancel := context.WithTimeout(context.Background(), defaultRPCTimeout) + defer cancel() + status, _ := client.GetStatusAuto(ctx, &empty.Empty{}) + + if status.Status { + log.Info("Auto tracked files is enabled") + } else { + log.Info("Auto tracked files is disabled") + } + + return nil +} + func (c *cli) CmdRemove(args ...string) error { cmd := SubCmd("remove", "IDENTIFIER", "Remove an existing mirror") force := cmd.Bool("f", false, "Never prompt for confirmation") diff --git a/config/config.go b/config/config.go index 4650575f..4abda51a 100644 --- a/config/config.go +++ b/config/config.go @@ -55,11 +55,14 @@ func defaultConfig() Configuration { SHA256: true, MD5: false, }, - DisallowRedirects: false, - WeightDistributionRange: 1.5, - DisableOnMissingFile: false, - RPCListenAddress: "localhost:3390", - RPCPassword: "", + DisallowRedirects: false, + WeightDistributionRange: 1.5, + DisableOnMissingFile: false, + RPCListenAddress: "localhost:3390", + RPCPassword: "", + MetricsEnabled: false, + MetricsTopFilesRetention: 0, + MetricsAutoTrackedFiles: false, } } @@ -95,6 +98,10 @@ type Configuration struct { RPCListenAddress string `yaml:"RPCListenAddress"` RPCPassword string `yaml:"RPCPassword"` + + MetricsEnabled bool `yaml:"MetricsEnabled"` + MetricsTopFilesRetention int `yaml:"MetricsTopFilesRetention"` + MetricsAutoTrackedFiles bool `yaml:"MetricsAutoTrackedFiles"` } type fallback struct { @@ -167,6 +174,9 @@ func ReloadConfig() error { if c.RepositoryScanInterval < 0 { c.RepositoryScanInterval = 0 } + if c.MetricsTopFilesRetention < 0 { + c.MetricsTopFilesRetention = 0 + } if config != nil && (c.RedisAddress != config.RedisAddress || diff --git a/database/utils.go b/database/utils.go index f2203514..e0d6ff48 100644 --- a/database/utils.go +++ b/database/utils.go @@ -4,10 +4,12 @@ package database import ( - "errors" "strconv" "github.com/gomodule/redigo/redis" + "github.com/pkg/errors" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) func (r *Redis) GetListOfMirrors() (map[int]string, error) { @@ -41,6 +43,158 @@ func (r *Redis) GetListOfMirrors() (map[int]string, error) { return mirrors, nil } +func (r *Redis) GetListOfFiles() ([]string, error) { + conn, err := r.Connect() + if err != nil { + return nil, err + } + defer conn.Close() + + values, err := redis.Values(conn.Do("SMEMBERS", "FILES")) + if err != nil { + return nil, err + } + + files := make([]string, len(values)) + for i, v := range values { + value, okValue := v.([]byte) + if !okValue { + return nil, errors.New("invalid type for file") + } + files[i] = string(value) + } + + return files, nil +} + +func (r *Redis) GetListOfTrackedFiles() ([]string, error) { + conn, err := r.Connect() + if err != nil { + return nil, err + } + defer conn.Close() + + values, err := redis.Values(conn.Do("SMEMBERS", "TRACKED_FILES")) + if err != nil { + return nil, err + } + + files := make([]string, len(values)) + for i, v := range values { + value, okValue := v.([]byte) + if !okValue { + return nil, errors.New("invalid type for file") + } + files[i] = string(value) + } + + return files, nil +} + +func (r *Redis) GetListOfCountries() ([]string, error) { + conn, err := r.Connect() + if err != nil { + return nil, err + } + defer conn.Close() + + values, err := redis.Values(conn.Do("SMEMBERS", "COUNTRIES")) + if err != nil { + return nil, err + } + + countries := make([]string, len(values)) + for i, v := range values { + value, okValue := v.([]byte) + if !okValue { + return nil, errors.New("invalid type for countries") + } + countries[i] = string(value) + } + + return countries, nil +} + +func (r *Redis) GetListOfTrackFilesFields(file string) ([]string, error) { + conn, err := r.Connect() + if err != nil { + return nil, err + } + defer conn.Close() + + values, err := redis.Values(conn.Do("HKEYS", "STATS_TRACKED_"+file)) + if err != nil { + return nil, err + } + + files := make([]string, len(values)) + for i, v := range values { + value, okValue := v.([]byte) + if !okValue { + return nil, errors.New("invalid type for file") + } + files[i] = string(value) + } + + return files, nil +} + +func (r *Redis) AddCountry(country string) error { + conn, err := r.Connect() + if err != nil { + return err + } + defer conn.Close() + _, err = conn.Do("SADD", "COUNTRIES", country) + return err +} + +func (r *Redis) AddTrackedFile(file string) error { + conn, err := r.Connect() + if err != nil { + return err + } + defer conn.Close() + + exists, err := redis.Int(conn.Do("SISMEMBER", "FILES", file)) + if err != nil { + return errors.Wrap(err, "failed to check for file presence") + } else if exists == 0 { + return status.Error(codes.FailedPrecondition, + "file does not exist") + } + + exists, err = redis.Int(conn.Do("SADD", "TRACKED_FILES", file)) + if err != nil { + return errors.Wrap(err, "failed to add file to metrics") + } else if exists == 0 { + return status.Error(codes.AlreadyExists, "file already is in metrics") + } + return nil +} + +func (r *Redis) DeleteTrackedFile(file string) error { + conn, err := r.Connect() + if err != nil { + return err + } + defer conn.Close() + + exists, err := redis.Int(conn.Do("SISMEMBER", "TRACKED_FILES", file)) + if err != nil { + return errors.Wrap(err, "failed to check for file presence") + } else if exists == 0 { + return status.Error(codes.FailedPrecondition, + "file is not part of tracked files") + } + + _, err = conn.Do("SREM", "TRACKED_FILES", file) + if err != nil { + return errors.Wrap(err, "failed to remove file from tracked files") + } + return nil +} + type NetReadyError struct { error } diff --git a/http/http.go b/http/http.go index 7b3a71e5..89d1ad3b 100644 --- a/http/http.go +++ b/http/http.go @@ -21,6 +21,7 @@ import ( "time" systemd "github.com/coreos/go-systemd/daemon" + "github.com/etix/mirrorbits/config" . "github.com/etix/mirrorbits/config" "github.com/etix/mirrorbits/core" "github.com/etix/mirrorbits/database" @@ -72,6 +73,7 @@ type HTTP struct { Restarting bool stopped bool stoppedMutex sync.Mutex + metrics *Metrics } // Templates is a struct embedding instances of the precompiled templates @@ -95,6 +97,15 @@ func HTTPServer(redis *database.Redis, cache *mirrors.Cache) *HTTP { h.engine = DefaultEngine{} http.Handle("/", NewGzipHandler(h.requestDispatcher)) + if config.GetConfig().MetricsEnabled { + log.Info("Metrics enabled") + h.metrics = NewMetrics(redis) + } else { + h.metrics = nil + log.Info("Metrics disabled") + } + http.Handle("/metrics", NewGzipHandler(h.metricsHandler)) + // Load the GeoIP databases if err := h.geoip.LoadGeoIP(); err != nil { if gerr, ok := err.(network.GeoIPError); ok { @@ -157,6 +168,14 @@ func (h *HTTP) Reload() { // Reload the GeoIP database h.geoip.LoadGeoIP() + if config.GetConfig().MetricsEnabled && h.metrics == nil { + log.Info("Configuration Reload: Metrics enabled") + h.metrics = NewMetrics(h.redis) + } else if h.metrics != nil { + h.metrics = nil + log.Info("Configuration Reload: Metrics disabled") + } + // Reload the templates h.templates.Lock() if t, err := h.LoadTemplates("mirrorlist"); err == nil { @@ -246,7 +265,7 @@ func (h *HTTP) requestDispatcher(w http.ResponseWriter, r *http.Request) { // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: -// +// // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above @@ -256,7 +275,7 @@ func (h *HTTP) requestDispatcher(w http.ResponseWriter, r *http.Request) { // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. -// +// // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR @@ -275,9 +294,9 @@ func isZeroTime(t time.Time) bool { } func setLastModified(w http.ResponseWriter, modtime time.Time) { - if !isZeroTime(modtime) { - w.Header().Set("Last-Modified", modtime.UTC().Format(TimeFormat)) - } + if !isZeroTime(modtime) { + w.Header().Set("Last-Modified", modtime.UTC().Format(TimeFormat)) + } } func checkIfModifiedSince(r *http.Request, modtime time.Time) condResult { @@ -347,7 +366,13 @@ func (h *HTTP) mirrorHandler(w http.ResponseWriter, r *http.Request, ctx *Contex return } - remoteIP := network.ExtractRemoteIP(r.Header.Get("X-Forwarded-For")) + var remoteIP string + // This http header should only be used in the context of testing geoip + if r.Header.Get("X-Fake-Ip") != "" { + remoteIP = network.ExtractRemoteIP(r.Header.Get("X-Fake-Ip")) + } else { + remoteIP = network.ExtractRemoteIP(r.Header.Get("X-Forwarded-For")) + } if len(remoteIP) == 0 { remoteIP = network.RemoteIPFromAddr(r.RemoteAddr) } @@ -361,6 +386,14 @@ func (h *HTTP) mirrorHandler(w http.ResponseWriter, r *http.Request, ctx *Contex clientInfo := h.geoip.GetRecord(remoteIP) //TODO return a pointer? + if clientInfo.Country != "" { + err = h.redis.AddCountry(clientInfo.Country) + } + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + mlist, excluded, err := h.engine.Selection(ctx, h.cache, &fileInfo, clientInfo) /* Handle errors */ @@ -382,6 +415,7 @@ func (h *HTTP) mirrorHandler(w http.ResponseWriter, r *http.Request, ctx *Contex sort.Sort(mirrors.ByRank{Mirrors: mlist, ClientInfo: clientInfo}) } else { // No fallback in stock, there's nothing else we can do + log.Error("503, fallback") http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable) return } @@ -430,14 +464,22 @@ func (h *HTTP) mirrorHandler(w http.ResponseWriter, r *http.Request, ctx *Contex http.Error(w, err.Error(), status) } + var isTracked = false + if config.GetConfig().MetricsEnabled { + isTracked, err = h.metrics.IsFileTracked(fileInfo.Path) + if err != nil { + log.Error("There was a problem fetching the tracked file list: ", err) + } + } + if !ctx.IsMirrorlist() { logs.LogDownload(resultRenderer.Type(), status, results, err) if len(mlist) > 0 { timeout := GetConfig().SameDownloadInterval if r.Header.Get("Range") == "" || timeout == 0 { - h.stats.CountDownload(mlist[0], fileInfo) + h.stats.CountDownload(mlist[0], fileInfo, clientInfo, isTracked) } else { - downloaderID := remoteIP+"/"+r.Header.Get("User-Agent") + downloaderID := remoteIP + "/" + r.Header.Get("User-Agent") hash := sha256.New() hash.Write([]byte(downloaderID)) chk := hex.EncodeToString(hash.Sum(nil)) @@ -445,7 +487,7 @@ func (h *HTTP) mirrorHandler(w http.ResponseWriter, r *http.Request, ctx *Contex rconn := h.redis.Get() defer rconn.Close() - tempKey := "DOWNLOADED_"+chk+"_"+urlPath + tempKey := "DOWNLOADED_" + chk + "_" + urlPath prev := "" if h.redis.IsAtLeastVersion("6.2.0") { @@ -461,10 +503,10 @@ func (h *HTTP) mirrorHandler(w http.ResponseWriter, r *http.Request, ctx *Contex // from counting multiple times a single client // downloading a single file in pieces, such as // torrent clients when files are used as web seeds. - h.stats.CountDownload(mlist[0], fileInfo) + h.stats.CountDownload(mlist[0], fileInfo, clientInfo, isTracked) } - if ! h.redis.IsAtLeastVersion("6.2.0") { + if !h.redis.IsAtLeastVersion("6.2.0") { // Set the key anyway to reset the timer. rconn.Send("SET", tempKey, 1, "EX", timeout) } diff --git a/http/metrics.go b/http/metrics.go new file mode 100644 index 00000000..2fabd3ad --- /dev/null +++ b/http/metrics.go @@ -0,0 +1,407 @@ +package http + +import ( + "fmt" + "net/http" + "regexp" + "sort" + "strconv" + "strings" + "sync" + "time" + + "github.com/etix/mirrorbits/config" + "github.com/etix/mirrorbits/database" + "github.com/gomodule/redigo/redis" +) + +type Metrics struct { + metricsResponse string + lock sync.Mutex + trackedFileList []string +} + +type metricsUnit struct { + Day int + Month int + Year int + Total int +} + +// NewMetrics returns a new instance of metrics +func NewMetrics(r *database.Redis) *Metrics { + metrics := Metrics{ + metricsResponse: "", + } + go func() { + for { + metrics.getMetrics(r) + trackedFiles, err := r.GetListOfTrackedFiles() + if err != nil { + log.Error("WTF") + log.Error(err.Error) + } else { + metrics.trackedFileList = trackedFiles + } + time.Sleep(2 * time.Minute) + } + }() + return &metrics +} + +func (m *Metrics) IsFileTracked(file string) (bool, error) { + for _, v := range m.trackedFileList { + if v == file { + return true, nil + } + } + return false, nil +} + +func statsToPrometheusFormat(metrics metricsUnit, labelName string, labelValue string) string { + var output string + + output += fmt.Sprintf("%s_total{%s=\"%s\"} %d\n", labelName, labelName, labelValue, metrics.Total) + output += fmt.Sprintf("%s_day{%s=\"%s\"} %d\n", labelName, labelName, labelValue, metrics.Day) + output += fmt.Sprintf("%s_month{%s=\"%s\"} %d\n", labelName, labelName, labelValue, metrics.Month) + output += fmt.Sprintf("%s_year{%s=\"%s\"} %d\n\n", labelName, labelName, labelValue, metrics.Year) + + return output +} + +func getSimpleMetrics(rconn redis.Conn, fields []string, name string, prefix string) (string, error) { + rconn.Send("MULTI") + today := time.Now() + for _, field := range fields { + rconn.Send("HGET", prefix, field) + rconn.Send("HGET", prefix+"_"+today.Format("2006_01_02"), field) + rconn.Send("HGET", prefix+"_"+today.Format("2006_01"), field) + rconn.Send("HGET", prefix+"_"+today.Format("2006"), field) + } + + stats, err := redis.Values(rconn.Do("EXEC")) + if err != nil { + return "", err + } + + index := 0 + output := "" + for _, field := range fields { + var metrics metricsUnit + metrics.Total, _ = redis.Int(stats[index], err) + metrics.Day, _ = redis.Int(stats[index+1], err) + metrics.Month, _ = redis.Int(stats[index+2], err) + metrics.Year, _ = redis.Int(stats[index+3], err) + output += statsToPrometheusFormat(metrics, name, field) + index += 4 + } + + return output, nil +} + +func (m *Metrics) getMetrics(httpRedis *database.Redis) { + rconn := httpRedis.Get() + defer rconn.Close() + + // Get all mirrors + // The output will be similar to: + // mirror_total{mirror="ftp-mirror"} 0 + // mirror_day{mirror="ftp-mirror"} 0 + // mirror_month{mirror="ftp-mirror"} 0 + // mirror_year{mirror="ftp-mirror"} 0 + // + // mirror_total{mirror="rsync-mirror"} 1046 + // mirror_day{mirror="rsync-mirror"} 12 + // mirror_month{mirror="rsync-mirror"} 1046 + // mirror_year{mirror="rsync-mirror"} 1046 + + mirrorsMap, err := httpRedis.GetListOfMirrors() + if err != nil { + log.Error("Cannot fetch the list of mirrors: " + err.Error()) + return + } + + var mirrorsIDs []int + for id := range mirrorsMap { + // We need a common order to iterate the + // results from Redis. + mirrorsIDs = append(mirrorsIDs, id) + } + + rconn.Send("MULTI") + today := time.Now() + for _, id := range mirrorsIDs { + rconn.Send("HGET", "STATS_MIRROR", id) + rconn.Send("HGET", "STATS_MIRROR_"+today.Format("2006_01_02"), id) + rconn.Send("HGET", "STATS_MIRROR_"+today.Format("2006_01"), id) + rconn.Send("HGET", "STATS_MIRROR_"+today.Format("2006"), id) + } + + stats, err := redis.Values(rconn.Do("EXEC")) + if err != nil { + log.Error("Cannot fetch stats: " + err.Error()) + return + } + + if len(stats) == 0 { + log.Info("Metrics: no files") + return + } + + var index int64 + var output string + for _, id := range mirrorsIDs { + var mirror metricsUnit + mirror.Total, _ = redis.Int(stats[index], err) + mirror.Day, _ = redis.Int(stats[index+1], err) + mirror.Month, _ = redis.Int(stats[index+2], err) + mirror.Year, _ = redis.Int(stats[index+3], err) + output += statsToPrometheusFormat(mirror, "mirror", mirrorsMap[id]) + index += 4 + } + + // Get all files + // The output will be similar to: + // file_total{file="/file_1.txt"} 538 + // file_day{file="/file_1.txt"} 9 + // file_month{file="/file_1.txt"} 538 + // file_year{file="/file_1.txt"} 538 + // + // file_total{file="/file_2.txt"} 508 + // file_day{file="/file_2.txt"} 3 + // file_month{file="/file_2.txt"} 508 + // file_year{file="/file_2.txt"} 508 + + var fileList []string + fileList, err = httpRedis.GetListOfFiles() + if err != nil { + log.Error("Cannot fetch list of files: " + err.Error()) + return + } + + fileOutput, err := getSimpleMetrics(rconn, fileList, "file", "STATS_FILE") + if err != nil { + log.Error("Cannot fetch stats: " + err.Error()) + return + } + + index = 0 + for _, name := range fileList { + var metrics metricsUnit + metrics.Total, _ = redis.Int(stats[index], err) + metrics.Day, _ = redis.Int(stats[index+1], err) + metrics.Month, _ = redis.Int(stats[index+2], err) + metrics.Year, _ = redis.Int(stats[index+3], err) + output += statsToPrometheusFormat(metrics, "file", name) + index += 4 + } + output += fileOutput + + // Get all countries + // The output will be similar to: + // country_total{country="France"} 501 + // country_day{country="France"} 7 + // country_month{country="France"} 501 + // country_year{country="France"} 501 + // + // country_total{country="United States"} 545 + // country_day{country="United States"} 5 + // country_month{country="United States"} 545 + // country_year{country="United States"} 545 + var countryList []string + countryList, err = httpRedis.GetListOfCountries() + if err != nil { + log.Error("Cannot fetch list of countries: " + err.Error()) + return + } + + countryOutput, err := getSimpleMetrics(rconn, countryList, "country", "STATS_COUNTRY") + if err != nil { + log.Error("Cannot fetch stats: " + err.Error()) + return + } + output += countryOutput + + // Get all tracked files download counts + + // Each STATS_TRACKED_* hash has the following format: + // n) Country_MirrorID + // n+1) Download count for the combination mirror / country + + // So that there is no interruption in the count timeseries, every time + // a new country_mirrorid pair is introduced, it must export a value + // even if there is no database hash for the current day. + // Hence we have to get all fields in the current STATS_TRACKED_* hashes + // and complete with fields that were in previous hashes but set to 0. + // For this, fields in STATS_TRACKED_filename without any date (the total) + // can be used + + // The output will be similar to: + // stats_tracked_{file="/file_1.txt",country="France",mirror="rsync-mirror"} 14 + // stats_tracked_{file="/file_1.txt",country="United States",mirror="rsync-mirror"} 11 + // + // stats_tracked_day{file="/file_1.txt",country="France",mirror="rsync-mirror"} 14 + // stats_tracked_day{file="/file_1.txt",country="United States",mirror="rsync-mirror"} 11 + // + // stats_tracked_month{file="/file_1.txt",country="France",mirror="rsync-mirror"} 14 + // stats_tracked_month{file="/file_1.txt",country="United States",mirror="rsync-mirror"} 11 + // + // stats_tracked_year{file="/file_1.txt",country="France",mirror="rsync-mirror"} 14 + // stats_tracked_year{file="/file_1.txt",country="United States",mirror="rsync-mirror"} 11 + // + // stats_top{file="/file_1.txt",country="France",mirror="rsync-mirror"} 14 + // stats_top{file="/file_1.txt",country="United States",mirror="rsync-mirror"} 11 + + mkeyList := make([]string, 0) + mkeyFileMap := make(map[string]string) + fileFieldsMap := make(map[string][]string) + + // Get tracked files downloads per country and mirror + for _, file := range m.trackedFileList { + mkey := "STATS_TRACKED_" + file + "_" + today.Format("2006_01_02") + // Getting all Country_MirrorID fields for that file + fileFieldsMap[file], err = httpRedis.GetListOfTrackFilesFields(file) + if err != nil { + log.Error("Failed to fetch file fields: ", err.Error()) + return + } + for i := 0; i < 4; i++ { + mkeyList = append(mkeyList, mkey) + mkeyFileMap[mkey] = file + mkey = mkey[:strings.LastIndex(mkey, "_")] + } + } + + rconn.Send("MULTI") + for _, mkey := range mkeyList { + rconn.Send("HGETALL", mkey) + } + stats, err = redis.Values(rconn.Do("EXEC")) + if err != nil { + log.Error("Cannot fetch files per country stats: " + err.Error()) + return + } + + index = 0 + // Get all download count information from the previous redis querries + for _, mkey := range mkeyList { + hashSlice, _ := redis.ByteSlices(stats[index], err) + file := mkeyFileMap[mkey] + fieldList := make([]string, len(fileFieldsMap[file])) + copy(fieldList, fileFieldsMap[file]) + sort.Strings(fieldList) + // This loop gets the download count from the database + for i := 0; i < len(hashSlice); i += 2 { + field, _ := redis.String(hashSlice[i], err) + value, _ := redis.Int(hashSlice[i+1], err) + sep := strings.Index(field, "_") + country := field[:sep] + mirrorID, err := strconv.Atoi(field[sep+1:]) + if err != nil { + log.Error("Failed to convert mirror ID: ", err) + return + } + mirror := mirrorsMap[mirrorID] + durationStr := getDurationFromKey(mkey) + output += fmt.Sprintf("stats_tracked_"+ + "%s{file=\"%s\",country=\"%s\",mirror=\"%s\"} %d\n", + durationStr, mkeyFileMap[mkey], country, mirror, value, + ) + fieldIndex := sort.SearchStrings(fieldList, field) + if fieldIndex < len(fieldList) { + fieldList = append(fieldList[:fieldIndex], fieldList[fieldIndex+1:]...) + } + } + // This loop set the download count to 0 as their is no current entry + // in the database of downloads by these mirrors and countries + for _, field := range fieldList { + sep := strings.Index(field, "_") + country := field[:sep] + mirrorID, err := strconv.Atoi(field[sep+1:]) + if err != nil { + log.Error("Failed to convert mirror ID: ", err) + return + } + mirror := mirrorsMap[mirrorID] + durationStr := getDurationFromKey(mkey) + output += fmt.Sprintf("stats_tracked_"+ + "%s{file=\"%s\",country=\"%s\",mirror=\"%s\"} %d\n", + durationStr, mkeyFileMap[mkey], country, mirror, 0, + ) + } + index++ + output += "\n" + } + + // Get daily stats for top 10 + retention := config.GetConfig().MetricsTopFilesRetention + for nbDays := 0; nbDays < retention; nbDays++ { + day := today.AddDate(0, 0, nbDays*-1) + rconn.Send("MULTI") + for _, file := range fileList { + mkey := fmt.Sprintf("STATS_TOP_%s_%s", file, + day.Format("2006_01_02")) + rconn.Send("HGETALL", mkey) + } + stats, err = redis.Values(rconn.Do("EXEC")) + if err != nil { + log.Error("Cannot fetch file per country stats: " + err.Error()) + return + } + for index, stat := range stats { + hashSlice, _ := redis.ByteSlices(stat, err) + for i := 0; i < len(hashSlice); i += 2 { + field, _ := redis.String(hashSlice[i], err) + value, _ := redis.Int(hashSlice[i+1], err) + sep := strings.Index(field, "_") + country := field[:sep] + mirrorID, err := strconv.Atoi(field[sep+1:]) + if err != nil { + log.Error("Failed to convert mirror ID: ", err) + return + } + mirror := mirrorsMap[mirrorID] + output += fmt.Sprintf("stats_top"+ + "{file=\"%s\",country=\"%s\",mirror=\"%s\"} %d\n", + fileList[index], country, mirror, value, + ) + } + } + } + + m.lock.Lock() + m.metricsResponse = output + m.lock.Unlock() + return +} + +func (h *HTTP) metricsHandler(w http.ResponseWriter, r *http.Request) { + if config.GetConfig().MetricsEnabled { + h.metrics.lock.Lock() + output := h.metrics.metricsResponse + h.metrics.lock.Unlock() + w.Write([]byte(output)) + log.Debug("test") + } else { + log.Errorf("Error: metrics are disabled") + http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable) + } + +} + +func getDurationFromKey(key string) string { + dayPattern := regexp.MustCompile("([0-9]{4}_[0-9]{2}_[0-9]{2})\\b") + monthPattern := regexp.MustCompile("([0-9]{4}_[0-9]{2})\\b") + yearPattern := regexp.MustCompile("([0-9]{4})\\b") + var durationStr string + if len(dayPattern.FindAllStringIndex(key, -1)) > 0 { + durationStr = "day" + } else if len(monthPattern.FindAllStringIndex(key, -1)) > 0 { + durationStr = "month" + } else if len(yearPattern.FindAllStringIndex(key, -1)) > 0 { + durationStr = "year" + } else { + durationStr = "total" + } + return durationStr +} diff --git a/http/stats.go b/http/stats.go index 513a946b..cbfcae84 100644 --- a/http/stats.go +++ b/http/stats.go @@ -11,9 +11,11 @@ import ( "sync" "time" + "github.com/etix/mirrorbits/config" "github.com/etix/mirrorbits/database" "github.com/etix/mirrorbits/filesystem" "github.com/etix/mirrorbits/mirrors" + "github.com/etix/mirrorbits/network" ) /* @@ -51,8 +53,10 @@ type Stats struct { type countItem struct { mirrorID int filepath string + country string size int64 time time.Time + tracked bool } // NewStats returns an instance of the stats counter @@ -75,7 +79,7 @@ func (s *Stats) Terminate() { } // CountDownload is a lightweight method used to count a new download for a specific file and mirror -func (s *Stats) CountDownload(m mirrors.Mirror, fileinfo filesystem.FileInfo) error { +func (s *Stats) CountDownload(m mirrors.Mirror, fileinfo filesystem.FileInfo, clientInfo network.GeoIPRecord, isTracked bool) error { if m.Name == "" { return errUnknownMirror } @@ -83,7 +87,7 @@ func (s *Stats) CountDownload(m mirrors.Mirror, fileinfo filesystem.FileInfo) er return errEmptyFileError } - s.countChan <- countItem{m.ID, fileinfo.Path, fileinfo.Size, time.Now().UTC()} + s.countChan <- countItem{m.ID, fileinfo.Path, clientInfo.Country, fileinfo.Size, time.Now().UTC(), isTracked} return nil } @@ -100,9 +104,18 @@ func (s *Stats) processCountDownload() { return case c := <-s.countChan: date := c.time.Format("2006_01_02|") // Includes separator + mirrorID := strconv.Itoa(c.mirrorID) + if c.country == "" { + c.country = "Unknown" + } s.mapStats["f"+date+c.filepath]++ - s.mapStats["m"+date+strconv.Itoa(c.mirrorID)]++ - s.mapStats["s"+date+strconv.Itoa(c.mirrorID)] += c.size + s.mapStats["m"+date+mirrorID]++ + s.mapStats["s"+date+mirrorID] += c.size + s.mapStats["c"+date+c.country]++ + s.mapStats["S"+date+c.country] += c.size + if c.tracked { + s.mapStats["F"+date+c.filepath+"|"+c.country+"_"+mirrorID]++ + } case <-pushTicker.C: s.pushStats() } @@ -127,8 +140,8 @@ func (s *Stats) pushStats() { return } + topFileRetention := config.GetConfig().MetricsTopFilesRetention rconn.Send("MULTI") - for k, v := range s.mapStats { if v == 0 { continue @@ -173,6 +186,48 @@ func (s *Stats) pushStats() { rconn.Send("HINCRBY", mkey, object, v) mkey = mkey[:strings.LastIndex(mkey, "_")] } + } else if typ == "c" { + // Country + + mkey := fmt.Sprintf("STATS_COUNTRY_%s", date) + + for i := 0; i < 4; i++ { + rconn.Send("HINCRBY", mkey, object, v) + mkey = mkey[:strings.LastIndex(mkey, "_")] + } + } else if typ == "S" { + // Country Bytes + + mkey := fmt.Sprintf("STATS_COUNTRY_BYTE_%s", date) + + for i := 0; i < 4; i++ { + rconn.Send("HINCRBY", mkey, object, v) + mkey = mkey[:strings.LastIndex(mkey, "_")] + } + } else if typ == "F" { + // File downloads per country + + sep := strings.LastIndex(object, "|") + file := object[:sep] + key := object[sep+1:] + mkey := fmt.Sprintf("STATS_TRACKED_%s_%s", file, date) + + for i := 0; i < 4; i++ { + rconn.Send("HINCRBY", mkey, key, v) + mkey = mkey[:strings.LastIndex(mkey, "_")] + } + if topFileRetention != 0 { + mkey = fmt.Sprintf("STATS_TOP_%s_%s", file, date) + rconn.Send("HINCRBY", mkey, key, v) + t, err := time.Parse("2006_01_02", date) + if err != nil { + log.Error("Failed to parse date: ", err.Error()) + return + } + t = t.AddDate(0, 0, topFileRetention) + topExpireDate := t.Format("2006_01_02") + rconn.Send("EXPIREAT", mkey, topExpireDate) + } } else { log.Warning("Stats: unknown type", typ) } diff --git a/main.go b/main.go index cacaa52b..4db90ed7 100644 --- a/main.go +++ b/main.go @@ -104,12 +104,13 @@ func main() { } case syscall.SIGHUP: listenAddress := GetConfig().ListenAddress + metricsEnabled := GetConfig().MetricsEnabled if err := ReloadConfig(); err != nil { log.Warningf("SIGHUP Received: %s\n", err) } else { log.Notice("SIGHUP Received: Reloading configuration...") } - if GetConfig().ListenAddress != listenAddress { + if GetConfig().ListenAddress != listenAddress || GetConfig().MetricsEnabled != metricsEnabled { h.Restarting = true h.Stop(1 * time.Second) } diff --git a/mirrorbits.conf b/mirrorbits.conf index eccd358c..0f398340 100644 --- a/mirrorbits.conf +++ b/mirrorbits.conf @@ -1,136 +1,7 @@ # vim: set ft=yaml: -################### -##### GENERAL ##### -################### - -## Path to the local repository -# Repository: /srv/repo - -## Path to the templates (default autodetect) -# Templates: /usr/share/mirrorbits/ - -## A local path or URL containing the JavaScript used by the templates. -## If this is not set (the default), the JavaScript will just be loaded -## from the usual CDNs. See also `contrib/localjs/fetchfiles.sh`. -# LocalJSPath: - -## Path where to store logs (comment to disable) -# LogDir: /var/log/mirrorbits - -## Path to the GeoIP2 mmdb databases -# GeoipDatabasePath: /usr/share/GeoIP/ - -## OutputMode can take on the three values: -## - redirect: HTTP redirect to the destination file on the selected mirror -## - json: return a json document for pre-treatment by an application -## - auto: based on the Accept HTTP header -# OutputMode: auto - -## Enable Gzip compression -# Gzip: false - -## Interval in seconds between which 2 range downloads of a given file -## from a same origin (hashed (IP, user-agent) couple) are considered -## to be the same download. In particular, download statistics are not -## incremented for this file. -# SameDownloadInterval: 600 - -## Host and port to listen on -# ListenAddress: :8080 - -## Host and port to listen for the CLI RPC -# RPCListenAddress: localhost:3390 - -## Password for restricting access to the CLI (optional) -# RPCPassword: - -#################### -##### DATABASE ##### -#################### - -## Redis host and port -# RedisAddress: 10.0.0.1:6379 - -## Redis password (if any) -# RedisPassword: supersecure - -## Redis database ID (if any) -# RedisDB: 0 - -## Redis sentinel name (only if using sentinel) -# RedisSentinelMasterName: mirrorbits - -## List of Redis sentinel hosts (only if using sentinel) -# RedisSentinels: -# - Host: 10.0.0.1:26379 -# - Host: 10.0.0.2:26379 -# - Host: 10.0.0.3:26379 - -################### -##### MIRRORS ##### -################### - -## Relative path to the trace file within the repository (optional). -## The file must contain the number of seconds since epoch and should -## be updated every minute (or so) with a cron on the master repository. -# TraceFileLocation: /trace - -## Interval between two scans of the local repository. -## The repository scan will index new and removed files and collect file -## sizes and checksums. -## This should, more or less, match the frequency where the local repo -## is updated. -# RepositoryScanInterval: 5 - -## Enable or disable specific hashing algorithms -# Hashes: -# SHA256: On -# SHA1: Off -# MD5: Off - -################### -##### MIRRORS ##### -################### - -## Maximum number of concurrent mirror synchronization to do (rsync/ftp) -# ConcurrentSync: 5 - -## Interval in minutes between mirror scan -# ScanInterval: 30 - -## Interval in minutes between mirrors HTTP health checks -# CheckInterval: 1 - -## Allow a mirror to issue an HTTP redirect. -## Setting this to true will disable the mirror if a redirect is detected. -# DisallowRedirects: false - -## Disable a mirror if an active file is missing (HTTP 404) -# DisableOnMissingFile: false - -## Adjust the weight/range of the geographic distribution -# WeightDistributionRange: 1.5 - -## Maximum number of alternative links to return in the HTTP header -# MaxLinkHeaders: 10 - -## Automatically fix timezone offsets. -## Enable this if one or more mirrors are always excluded because their -## last-modification-time mismatch. This option will try to guess the -## offset and adjust the mod time accordingly. -## Affected mirrors will need to be rescanned after enabling this feature. -# FixTimezoneOffsets: false - -## List of mirrors to use as fallback which will be used in case mirrorbits -## is unable to answer a request because the database is unreachable. -## Note: Mirrorbits will redirect to one of these mirrors based on the user -## location but won't be able to know if the mirror has the requested file. -## Therefore only put your most reliable and up-to-date mirrors here. -# Fallbacks: -# - URL: http://fallback1.mirror/repo/ -# CountryCode: fr -# ContinentCode: eu -# - URL: http://fallback2.mirror/repo/ -# CountryCode: us -# ContinentCode: na +Repository: /srv/repo +ListenAddress: :80 +RedisAddress: 172.18.0.1:6379 +LogDir: /var/log/mirrorbits +MetricsEnabled: true diff --git a/rpc/rpc.go b/rpc/rpc.go index cb9d7268..6746cd2c 100644 --- a/rpc/rpc.go +++ b/rpc/rpc.go @@ -803,3 +803,51 @@ func (c *CLI) GetMirrorLogs(ctx context.Context, in *GetMirrorLogsRequest) (*Get return &GetMirrorLogsReply{Line: lines}, nil } + +func (c *CLI) AddMetric(ctx context.Context, in *Metric) (*empty.Empty, error) { + return &empty.Empty{}, c.redis.AddTrackedFile(in.Filename) +} + +func (c *CLI) DelMetric(ctx context.Context, in *Metric) (*empty.Empty, error) { + return &empty.Empty{}, c.redis.DeleteTrackedFile(in.Filename) +} + +func (c *CLI) ListMetrics(ctx context.Context, in *Metric) (*MetricsList, error) { + conn, err := c.redis.Connect() + if err != nil { + return nil, err + } + defer conn.Close() + + values, err := redis.Values(conn.Do("SSCAN", "TRACKED_FILES", + "0", "MATCH", in.Filename, "COUNT", 1000)) + if err != nil { + return nil, err + } + + byteValues, err := redis.ByteSlices(values[1], err) + files := make([]string, len(byteValues)) + for i, v := range byteValues { + files[i] = string(v) + } + + return &MetricsList{Filename: files}, nil +} + +func (c *CLI) GetStatusMetrics(context.Context, *empty.Empty) (*Status, error) { + return &Status{Status: GetConfig().MetricsEnabled}, nil +} + +func (c *CLI) EnableAuto(context.Context, *empty.Empty) (*empty.Empty, error) { + GetConfig().MetricsAutoTrackedFiles = true + return &empty.Empty{}, nil +} + +func (c *CLI) DisableAuto(context.Context, *empty.Empty) (*empty.Empty, error) { + GetConfig().MetricsAutoTrackedFiles = false + return &empty.Empty{}, nil +} + +func (c *CLI) GetStatusAuto(context.Context, *empty.Empty) (*Status, error) { + return &Status{Status: GetConfig().MetricsAutoTrackedFiles}, nil +} diff --git a/rpc/rpc.pb.go b/rpc/rpc.pb.go index e5e8aadb..5ccec31b 100644 --- a/rpc/rpc.pb.go +++ b/rpc/rpc.pb.go @@ -1282,6 +1282,123 @@ func (m *GetMirrorLogsReply) GetLine() []string { return nil } +type Metric struct { + Filename string `protobuf:"bytes,1,opt,name=Filename,proto3" json:"Filename,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Metric) Reset() { *m = Metric{} } +func (m *Metric) String() string { return proto.CompactTextString(m) } +func (*Metric) ProtoMessage() {} +func (*Metric) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{20} +} + +func (m *Metric) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Metric.Unmarshal(m, b) +} +func (m *Metric) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Metric.Marshal(b, m, deterministic) +} +func (m *Metric) XXX_Merge(src proto.Message) { + xxx_messageInfo_Metric.Merge(m, src) +} +func (m *Metric) XXX_Size() int { + return xxx_messageInfo_Metric.Size(m) +} +func (m *Metric) XXX_DiscardUnknown() { + xxx_messageInfo_Metric.DiscardUnknown(m) +} + +var xxx_messageInfo_Metric proto.InternalMessageInfo + +func (m *Metric) GetFilename() string { + if m != nil { + return m.Filename + } + return "" +} + +type MetricsList struct { + Filename []string `protobuf:"bytes,1,rep,name=Filename,proto3" json:"Filename,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *MetricsList) Reset() { *m = MetricsList{} } +func (m *MetricsList) String() string { return proto.CompactTextString(m) } +func (*MetricsList) ProtoMessage() {} +func (*MetricsList) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{21} +} + +func (m *MetricsList) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_MetricsList.Unmarshal(m, b) +} +func (m *MetricsList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_MetricsList.Marshal(b, m, deterministic) +} +func (m *MetricsList) XXX_Merge(src proto.Message) { + xxx_messageInfo_MetricsList.Merge(m, src) +} +func (m *MetricsList) XXX_Size() int { + return xxx_messageInfo_MetricsList.Size(m) +} +func (m *MetricsList) XXX_DiscardUnknown() { + xxx_messageInfo_MetricsList.DiscardUnknown(m) +} + +var xxx_messageInfo_MetricsList proto.InternalMessageInfo + +func (m *MetricsList) GetFilename() []string { + if m != nil { + return m.Filename + } + return nil +} + +type Status struct { + Status bool `protobuf:"varint,1,opt,name=Status,proto3" json:"Status,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Status) Reset() { *m = Status{} } +func (m *Status) String() string { return proto.CompactTextString(m) } +func (*Status) ProtoMessage() {} +func (*Status) Descriptor() ([]byte, []int) { + return fileDescriptor_77a6da22d6a3feb1, []int{22} +} + +func (m *Status) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Status.Unmarshal(m, b) +} +func (m *Status) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Status.Marshal(b, m, deterministic) +} +func (m *Status) XXX_Merge(src proto.Message) { + xxx_messageInfo_Status.Merge(m, src) +} +func (m *Status) XXX_Size() int { + return xxx_messageInfo_Status.Size(m) +} +func (m *Status) XXX_DiscardUnknown() { + xxx_messageInfo_Status.DiscardUnknown(m) +} + +var xxx_messageInfo_Status proto.InternalMessageInfo + +func (m *Status) GetStatus() bool { + if m != nil { + return m.Status + } + return false +} + func init() { proto.RegisterEnum("ScanMirrorRequest_Method", ScanMirrorRequest_Method_name, ScanMirrorRequest_Method_value) proto.RegisterType((*VersionReply)(nil), "VersionReply") @@ -1305,6 +1422,9 @@ func init() { proto.RegisterType((*StatsMirrorReply)(nil), "StatsMirrorReply") proto.RegisterType((*GetMirrorLogsRequest)(nil), "GetMirrorLogsRequest") proto.RegisterType((*GetMirrorLogsReply)(nil), "GetMirrorLogsReply") + proto.RegisterType((*Metric)(nil), "Metric") + proto.RegisterType((*MetricsList)(nil), "MetricsList") + proto.RegisterType((*Status)(nil), "Status") } func init() { @@ -1312,98 +1432,105 @@ func init() { } var fileDescriptor_77a6da22d6a3feb1 = []byte{ - // 1445 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0x4d, 0x73, 0x1b, 0x45, - 0x13, 0xd6, 0x4a, 0xb6, 0x65, 0xb5, 0x64, 0x5b, 0x1e, 0x3b, 0x7e, 0x37, 0x4a, 0xde, 0x44, 0x19, - 0x3e, 0x22, 0x8a, 0x62, 0x43, 0x4c, 0x02, 0xae, 0x10, 0xa0, 0x84, 0xfc, 0x11, 0x83, 0x1c, 0xbb, - 0x56, 0x31, 0x14, 0xdc, 0x36, 0xda, 0x91, 0xbc, 0xc5, 0x6a, 0x47, 0xec, 0x8c, 0x12, 0xab, 0x8a, - 0x9f, 0x41, 0x71, 0xe2, 0x00, 0x3f, 0x80, 0x2a, 0x8e, 0xfc, 0x3c, 0xaa, 0x67, 0x66, 0xa5, 0xd5, - 0xca, 0x1f, 0x90, 0x03, 0xb7, 0xed, 0xa7, 0x7b, 0xa6, 0x7b, 0x7a, 0xba, 0x9f, 0x9e, 0x85, 0x52, - 0x3c, 0xec, 0x3a, 0xc3, 0x98, 0x4b, 0x5e, 0xbb, 0xd5, 0xe7, 0xbc, 0x1f, 0xb2, 0x07, 0x4a, 0x7a, - 0x39, 0xea, 0x3d, 0x60, 0x83, 0xa1, 0x1c, 0x1b, 0xe5, 0xdd, 0xac, 0x52, 0x06, 0x03, 0x26, 0xa4, - 0x37, 0x18, 0x6a, 0x03, 0xfa, 0x9b, 0x05, 0x95, 0x6f, 0x58, 0x2c, 0x02, 0x1e, 0xb9, 0x6c, 0x18, - 0x8e, 0x89, 0x0d, 0x45, 0x23, 0xdb, 0x56, 0xdd, 0x6a, 0x94, 0xdc, 0x44, 0x24, 0x9b, 0xb0, 0xf8, - 0xe5, 0x28, 0x08, 0x7d, 0x3b, 0xaf, 0x70, 0x2d, 0x90, 0xdb, 0x50, 0x3a, 0xe0, 0xc9, 0x8a, 0x82, - 0xd2, 0x4c, 0x01, 0xb2, 0x0a, 0xf9, 0xe3, 0x8e, 0xbd, 0xa0, 0xe0, 0xfc, 0x71, 0x87, 0x10, 0x58, - 0x68, 0xc6, 0xdd, 0x33, 0x7b, 0x51, 0x21, 0xea, 0x9b, 0xdc, 0x01, 0x38, 0xe0, 0x47, 0xde, 0xf9, - 0x49, 0xcc, 0xbb, 0xc2, 0x5e, 0xaa, 0x5b, 0x8d, 0x45, 0x37, 0x85, 0xd0, 0x06, 0x54, 0x8e, 0x3c, - 0xd9, 0x3d, 0x73, 0xd9, 0x8f, 0x23, 0x26, 0x24, 0x46, 0x78, 0xe2, 0x49, 0xc9, 0xe2, 0x49, 0x84, - 0x46, 0xa4, 0xbf, 0x2c, 0xc3, 0xd2, 0x51, 0x10, 0xc7, 0x3c, 0x46, 0xc7, 0x87, 0xbb, 0x4a, 0xbf, - 0xe8, 0xe6, 0x0f, 0x77, 0xd1, 0xf1, 0x73, 0x6f, 0xc0, 0x4c, 0xec, 0xea, 0x1b, 0x37, 0x7a, 0x26, - 0xe5, 0xf0, 0xd4, 0x6d, 0x9b, 0xc0, 0x13, 0x91, 0xd4, 0x60, 0xd9, 0x15, 0xe3, 0xa8, 0x8b, 0x2a, - 0x1d, 0xfc, 0x44, 0x26, 0x5b, 0xb0, 0xb4, 0xaf, 0x17, 0xe9, 0x43, 0x18, 0x89, 0xd4, 0xa1, 0xdc, - 0x19, 0xf2, 0x48, 0xf0, 0x58, 0x39, 0x5a, 0x52, 0xca, 0x34, 0x84, 0x07, 0x35, 0x22, 0xae, 0x2e, - 0x2a, 0x83, 0x14, 0x42, 0xde, 0x85, 0x55, 0x23, 0xb5, 0x79, 0x9f, 0xa3, 0xcd, 0xb2, 0xb2, 0xc9, - 0xa0, 0x98, 0xf2, 0xa6, 0x3f, 0x08, 0x22, 0xe5, 0xa7, 0xa4, 0x53, 0x3e, 0x01, 0xd0, 0x8b, 0x12, - 0xf6, 0x06, 0x5e, 0x10, 0xda, 0xa0, 0xbd, 0x4c, 0x11, 0xd4, 0xb7, 0x46, 0x42, 0xf2, 0xc1, 0xae, - 0x27, 0x3d, 0xbb, 0xac, 0xf5, 0x53, 0x84, 0xbc, 0x0d, 0x2b, 0x2d, 0x1e, 0xc9, 0x20, 0x62, 0x91, - 0x3c, 0x8e, 0xc2, 0xb1, 0x5d, 0xa9, 0x5b, 0x8d, 0x65, 0x77, 0x16, 0xc4, 0xd3, 0xb6, 0xf8, 0x28, - 0x92, 0xf1, 0x58, 0xd9, 0xac, 0x28, 0x9b, 0x34, 0x84, 0x79, 0x6a, 0x76, 0x94, 0x72, 0x55, 0x29, - 0x8d, 0x84, 0x65, 0xd4, 0xe9, 0xf2, 0x98, 0xd9, 0x6b, 0xea, 0x72, 0xb4, 0x80, 0x19, 0x6f, 0x7b, - 0x32, 0x90, 0x23, 0x9f, 0xd9, 0xd5, 0xba, 0xd5, 0xc8, 0xbb, 0x13, 0x19, 0xcf, 0xdb, 0xe6, 0x51, - 0x5f, 0x2b, 0xd7, 0x95, 0x72, 0x0a, 0xcc, 0xc4, 0xdb, 0xe2, 0x3e, 0xb3, 0x89, 0x3a, 0xd2, 0x2c, - 0x48, 0x28, 0x54, 0x4c, 0x70, 0x28, 0x0a, 0x7b, 0x43, 0x19, 0xcd, 0x60, 0x64, 0x1b, 0x36, 0xf7, - 0xce, 0xbb, 0xe1, 0xc8, 0x67, 0xfe, 0x8c, 0xed, 0xa6, 0xb2, 0xbd, 0x50, 0x87, 0xa7, 0x69, 0x8a, - 0x68, 0x34, 0xb0, 0x6f, 0xd4, 0xad, 0xc6, 0x8a, 0xab, 0x05, 0xac, 0xac, 0x16, 0x1f, 0x0c, 0x58, - 0x24, 0xed, 0x2d, 0x5d, 0x59, 0x46, 0x44, 0xcd, 0x5e, 0xe4, 0xbd, 0x0c, 0x99, 0x6f, 0xff, 0x4f, - 0xa5, 0x25, 0x11, 0xb1, 0x62, 0x4f, 0x87, 0xb6, 0xad, 0xc0, 0xfc, 0xe9, 0x10, 0xcf, 0x65, 0x3c, - 0xba, 0xcc, 0x13, 0x3c, 0xb2, 0x6f, 0xea, 0x73, 0xcd, 0x80, 0xe4, 0x09, 0x40, 0x47, 0x7a, 0x92, - 0x75, 0x82, 0xa8, 0xcb, 0xec, 0x5a, 0xdd, 0x6a, 0x94, 0xb7, 0x6b, 0x8e, 0xee, 0x7a, 0x27, 0xe9, - 0x7a, 0xe7, 0x45, 0xd2, 0xf5, 0x6e, 0xca, 0x1a, 0xeb, 0xad, 0x19, 0x86, 0xfc, 0xb5, 0xcb, 0xfc, - 0x20, 0x66, 0x5d, 0x29, 0xec, 0x5b, 0xea, 0x4a, 0x32, 0x28, 0xf9, 0x18, 0xef, 0x46, 0xc8, 0xce, - 0x38, 0xea, 0xda, 0xb7, 0xaf, 0xf5, 0x30, 0xb1, 0x25, 0x5f, 0x01, 0x51, 0xdf, 0xa3, 0x6e, 0x97, - 0x09, 0xd1, 0x1b, 0x85, 0x6a, 0x87, 0xff, 0x5f, 0xbb, 0xc3, 0x05, 0xab, 0xc8, 0x53, 0x28, 0x23, - 0x7a, 0xc4, 0x7d, 0xb4, 0xb3, 0xef, 0x5c, 0xbb, 0x49, 0xda, 0x9c, 0x3e, 0x82, 0x35, 0xcd, 0x0b, - 0xed, 0x40, 0x48, 0xcd, 0x73, 0xf7, 0xa0, 0xa8, 0x21, 0x61, 0x5b, 0xf5, 0x42, 0xa3, 0xbc, 0x5d, - 0x74, 0xb4, 0xec, 0x26, 0x38, 0x75, 0x60, 0x59, 0x7f, 0x1e, 0xee, 0xfe, 0x13, 0x3e, 0xa1, 0x0f, - 0x01, 0x0c, 0x51, 0xa1, 0x83, 0xb7, 0xb2, 0x0e, 0x4a, 0x4e, 0xb2, 0xdb, 0xd4, 0xc5, 0x17, 0xb0, - 0xd1, 0x3a, 0xf3, 0xa2, 0x3e, 0xc3, 0x6b, 0x19, 0x89, 0x84, 0xe2, 0xb2, 0xde, 0x52, 0x55, 0x93, - 0x9f, 0xa9, 0x1a, 0x7a, 0x2f, 0x39, 0xd9, 0xe1, 0xee, 0x25, 0x8b, 0xe9, 0x9f, 0x16, 0xac, 0x36, - 0x7d, 0xdf, 0x9c, 0x4e, 0xc5, 0x96, 0xee, 0x36, 0xeb, 0xaa, 0x6e, 0xcb, 0x67, 0xbb, 0x4d, 0x55, - 0xb6, 0xaa, 0xff, 0x84, 0x33, 0x8d, 0x88, 0xeb, 0x26, 0x2d, 0x67, 0x48, 0x73, 0x0a, 0x90, 0x2a, - 0x14, 0x9a, 0x9d, 0xe7, 0x86, 0x32, 0xf1, 0x13, 0x63, 0xf8, 0xd6, 0x8b, 0xa3, 0x20, 0xea, 0x23, - 0xe9, 0x17, 0x90, 0x63, 0x13, 0x99, 0xde, 0x87, 0xf5, 0xd3, 0xa1, 0xef, 0x49, 0x96, 0x0e, 0x9a, - 0xc0, 0xc2, 0x6e, 0xd0, 0xeb, 0x19, 0xd2, 0x57, 0xdf, 0xb4, 0x0f, 0x9b, 0x07, 0x8c, 0xcf, 0xdb, - 0xde, 0x4d, 0x06, 0x81, 0xb2, 0x4e, 0x5d, 0x6e, 0x32, 0x1f, 0x92, 0xcd, 0xf2, 0xd3, 0xcd, 0x66, - 0x22, 0x2a, 0x64, 0x22, 0xda, 0x06, 0xdb, 0x65, 0xbd, 0x98, 0x09, 0xbc, 0x5d, 0x2e, 0x02, 0xc9, - 0xe3, 0x71, 0x92, 0xf0, 0x2d, 0x58, 0x72, 0xd9, 0x99, 0x27, 0xce, 0x94, 0xb3, 0x65, 0xd7, 0x48, - 0xf4, 0x77, 0x0b, 0xd6, 0x3b, 0x5d, 0x2f, 0x4a, 0x02, 0xbb, 0xf8, 0x6e, 0x91, 0xaf, 0x47, 0x92, - 0xeb, 0x0b, 0x35, 0xd7, 0x9b, 0x42, 0xc8, 0x63, 0x58, 0x3e, 0xc1, 0xf2, 0xee, 0xf2, 0x50, 0xa5, - 0x7c, 0x75, 0xfb, 0xa6, 0x33, 0xb7, 0xab, 0x73, 0xc4, 0xe4, 0x19, 0xf7, 0xdd, 0x89, 0x29, 0x7d, - 0x07, 0x96, 0x34, 0x46, 0x8a, 0x50, 0x68, 0xb6, 0xdb, 0xd5, 0x1c, 0x7e, 0xec, 0xbf, 0x38, 0xa9, - 0x5a, 0xa4, 0x04, 0x8b, 0x6e, 0xe7, 0xbb, 0xe7, 0xad, 0x6a, 0x9e, 0xfe, 0x61, 0xc1, 0x5a, 0x7a, - 0x37, 0xf3, 0x04, 0x48, 0xaa, 0xcd, 0x9a, 0xe5, 0x28, 0x0a, 0x95, 0xfd, 0x20, 0x64, 0xe2, 0x30, - 0xf2, 0xd9, 0xb9, 0x29, 0xc6, 0x82, 0x3b, 0x83, 0xa1, 0xcd, 0xd7, 0x11, 0x7f, 0x1d, 0x25, 0x36, - 0x05, 0x6d, 0x93, 0xc6, 0xd0, 0x83, 0xcb, 0x06, 0xfc, 0x15, 0xf3, 0x55, 0xa5, 0x14, 0xdc, 0x44, - 0xc4, 0x6c, 0xbc, 0xf8, 0xfe, 0xb8, 0xd7, 0x13, 0x4c, 0x1e, 0x09, 0x55, 0x2e, 0x05, 0x37, 0x85, - 0xd0, 0x5f, 0x2d, 0xa8, 0x62, 0xaf, 0x08, 0xf4, 0x79, 0xed, 0x8b, 0x80, 0xec, 0x40, 0x69, 0x17, - 0xf9, 0x4e, 0x7a, 0xb1, 0x54, 0xd1, 0x5e, 0x4d, 0x1a, 0x53, 0x63, 0xf2, 0x08, 0x8a, 0x28, 0xec, - 0x45, 0xfa, 0x04, 0x57, 0xaf, 0x4b, 0x4c, 0xe9, 0x4f, 0xb0, 0x9a, 0x8a, 0x0e, 0x93, 0xf9, 0x21, - 0x2c, 0xf6, 0x30, 0x3d, 0x86, 0x04, 0x6a, 0xce, 0xac, 0xde, 0x51, 0xb9, 0xdb, 0xc3, 0x0e, 0x72, - 0xb5, 0x61, 0x6d, 0x07, 0x60, 0x0a, 0x62, 0xe3, 0xfc, 0xc0, 0xc6, 0xe6, 0x5c, 0xf8, 0x89, 0x23, - 0xe7, 0x95, 0x17, 0x8e, 0x98, 0xc9, 0xbe, 0x16, 0x9e, 0xe4, 0x77, 0x2c, 0xfa, 0xb3, 0x05, 0x44, - 0x6d, 0x7f, 0x75, 0xc5, 0xfd, 0xd7, 0x49, 0x61, 0xe6, 0xca, 0xfe, 0x55, 0x83, 0xe2, 0x13, 0x4c, - 0xc7, 0x2f, 0xcc, 0x41, 0x27, 0xb2, 0x7a, 0x89, 0x8e, 0x25, 0x13, 0xa6, 0xb6, 0xb4, 0x40, 0xf7, - 0x91, 0x0b, 0xa4, 0xe1, 0x79, 0xde, 0x17, 0x57, 0x34, 0xdc, 0x91, 0x77, 0xee, 0x32, 0x31, 0x0a, - 0xcd, 0xde, 0x8b, 0x6e, 0x0a, 0xa1, 0x0d, 0x20, 0x99, 0x7d, 0x0c, 0xfb, 0x84, 0x41, 0xc4, 0xd4, - 0x35, 0x96, 0x5c, 0xf5, 0xbd, 0xfd, 0x57, 0x11, 0x0a, 0xad, 0xf6, 0x21, 0x79, 0x0c, 0x70, 0xc0, - 0x64, 0xf2, 0xe6, 0xdd, 0x9a, 0xcb, 0xc9, 0x1e, 0xbe, 0xc8, 0x6b, 0x2b, 0x4e, 0xfa, 0xa1, 0x4d, - 0x73, 0xe4, 0x53, 0x28, 0x9e, 0x0e, 0xfb, 0xb1, 0xe7, 0xb3, 0x4b, 0xd7, 0x5c, 0x82, 0xd3, 0x1c, - 0x79, 0x82, 0xa4, 0x13, 0x72, 0xcf, 0x7f, 0x83, 0xb5, 0x9f, 0x43, 0x25, 0x3d, 0x75, 0xc8, 0xa6, - 0x73, 0xc1, 0x10, 0xba, 0x62, 0xfd, 0x36, 0x2c, 0xe0, 0x20, 0xbd, 0xd4, 0x73, 0xd5, 0xc9, 0x4c, - 0x5b, 0x9a, 0x23, 0xef, 0x01, 0x98, 0x41, 0x15, 0xf5, 0x38, 0xa9, 0x3a, 0x99, 0xa9, 0x55, 0x4b, - 0x0a, 0x80, 0xe6, 0xc8, 0x7d, 0x7c, 0xdf, 0x9a, 0x79, 0x45, 0x12, 0xbc, 0xb6, 0xe6, 0xcc, 0x0e, - 0x31, 0x9a, 0x23, 0x1f, 0x40, 0x25, 0x4d, 0xfd, 0x53, 0x5b, 0xe2, 0xcc, 0x8d, 0x04, 0x95, 0xb2, - 0x8a, 0xa6, 0x19, 0x63, 0x3e, 0x1f, 0xc4, 0xe5, 0x47, 0x7e, 0x0a, 0x6b, 0x99, 0x41, 0x73, 0xc1, - 0xf2, 0x1b, 0xce, 0x45, 0xc3, 0x88, 0xe6, 0xc8, 0x33, 0x58, 0x9f, 0x9b, 0x1e, 0xe4, 0xa6, 0x73, - 0xd9, 0x44, 0xb9, 0x22, 0x8e, 0x47, 0x00, 0x53, 0xba, 0x26, 0x64, 0x7e, 0x12, 0xd4, 0xaa, 0x4e, - 0x86, 0xcf, 0x69, 0x8e, 0x3c, 0x84, 0xd2, 0x84, 0x76, 0xc8, 0xba, 0x93, 0x25, 0xd0, 0xda, 0x5a, - 0x86, 0x95, 0x68, 0x8e, 0x7c, 0x02, 0xe5, 0x54, 0xd3, 0x92, 0x0d, 0x67, 0x9e, 0x58, 0x6a, 0xeb, - 0x4e, 0xb6, 0xaf, 0x69, 0x8e, 0xec, 0xc0, 0xc2, 0x49, 0x10, 0xf5, 0xdf, 0xa0, 0x2c, 0x3f, 0x83, - 0x95, 0x99, 0xc6, 0x23, 0x98, 0xcf, 0xf9, 0x86, 0xae, 0x6d, 0x38, 0xf3, 0xfd, 0x49, 0x73, 0xe4, - 0x7d, 0x28, 0xab, 0xe7, 0x97, 0x89, 0x78, 0xc5, 0x49, 0xff, 0x35, 0xd6, 0xca, 0xce, 0xf4, 0x6d, - 0x46, 0x73, 0x2f, 0x97, 0x94, 0xf7, 0x8f, 0xfe, 0x0e, 0x00, 0x00, 0xff, 0xff, 0xc5, 0xae, 0x97, - 0x9a, 0x49, 0x0f, 0x00, 0x00, + // 1563 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0x51, 0x73, 0x1a, 0x47, + 0x12, 0x66, 0x41, 0x02, 0xd1, 0x20, 0x09, 0x8d, 0x64, 0xdd, 0x1a, 0xfb, 0x6c, 0x3c, 0xe7, 0xb3, + 0x71, 0x5d, 0xdd, 0xfa, 0xcc, 0xd9, 0x77, 0x2a, 0x9f, 0xef, 0x5c, 0x1c, 0x48, 0xb2, 0x12, 0x64, + 0xa9, 0x16, 0x2b, 0xa9, 0xe4, 0x6d, 0xcd, 0x0e, 0x68, 0x2b, 0xcb, 0x0e, 0xd9, 0x1d, 0x6c, 0x51, + 0x95, 0x9f, 0x91, 0xca, 0x53, 0x1e, 0x92, 0xf7, 0xa4, 0x2a, 0x3f, 0x31, 0xd5, 0x33, 0xb3, 0xb0, + 0x2c, 0x12, 0x72, 0x9c, 0xaa, 0xbc, 0x4d, 0x7f, 0xd3, 0xd3, 0xdd, 0xd3, 0xd3, 0xfd, 0xf5, 0x02, + 0x14, 0xc3, 0x51, 0xcf, 0x1a, 0x85, 0x5c, 0xf0, 0xea, 0xad, 0x01, 0xe7, 0x03, 0x9f, 0x3d, 0x96, + 0xd2, 0xdb, 0x71, 0xff, 0x31, 0x1b, 0x8e, 0xc4, 0x44, 0x6f, 0xde, 0x4d, 0x6f, 0x0a, 0x6f, 0xc8, + 0x22, 0xe1, 0x0c, 0x47, 0x4a, 0x81, 0xfe, 0x60, 0x40, 0xf9, 0x33, 0x16, 0x46, 0x1e, 0x0f, 0x6c, + 0x36, 0xf2, 0x27, 0xc4, 0x84, 0x82, 0x96, 0x4d, 0xa3, 0x66, 0xd4, 0x8b, 0x76, 0x2c, 0x92, 0x1d, + 0x58, 0xfd, 0xff, 0xd8, 0xf3, 0x5d, 0x33, 0x2b, 0x71, 0x25, 0x90, 0xdb, 0x50, 0x3c, 0xe4, 0xf1, + 0x89, 0x9c, 0xdc, 0x99, 0x01, 0x64, 0x03, 0xb2, 0x27, 0x5d, 0x73, 0x45, 0xc2, 0xd9, 0x93, 0x2e, + 0x21, 0xb0, 0xd2, 0x0c, 0x7b, 0xe7, 0xe6, 0xaa, 0x44, 0xe4, 0x9a, 0xdc, 0x01, 0x38, 0xe4, 0xc7, + 0xce, 0xc5, 0x69, 0xc8, 0x7b, 0x91, 0x99, 0xaf, 0x19, 0xf5, 0x55, 0x3b, 0x81, 0xd0, 0x3a, 0x94, + 0x8f, 0x1d, 0xd1, 0x3b, 0xb7, 0xd9, 0xd7, 0x63, 0x16, 0x09, 0x8c, 0xf0, 0xd4, 0x11, 0x82, 0x85, + 0xd3, 0x08, 0xb5, 0x48, 0xbf, 0x5b, 0x83, 0xfc, 0xb1, 0x17, 0x86, 0x3c, 0x44, 0xc7, 0x47, 0x6d, + 0xb9, 0xbf, 0x6a, 0x67, 0x8f, 0xda, 0xe8, 0xf8, 0xb5, 0x33, 0x64, 0x3a, 0x76, 0xb9, 0x46, 0x43, + 0xaf, 0x84, 0x18, 0x9d, 0xd9, 0x1d, 0x1d, 0x78, 0x2c, 0x92, 0x2a, 0xac, 0xd9, 0xd1, 0x24, 0xe8, + 0xe1, 0x96, 0x0a, 0x7e, 0x2a, 0x93, 0x5d, 0xc8, 0x1f, 0xa8, 0x43, 0xea, 0x12, 0x5a, 0x22, 0x35, + 0x28, 0x75, 0x47, 0x3c, 0x88, 0x78, 0x28, 0x1d, 0xe5, 0xe5, 0x66, 0x12, 0xc2, 0x8b, 0x6a, 0x11, + 0x4f, 0x17, 0xa4, 0x42, 0x02, 0x21, 0x0f, 0x60, 0x43, 0x4b, 0x1d, 0x3e, 0xe0, 0xa8, 0xb3, 0x26, + 0x75, 0x52, 0x28, 0xa6, 0xbc, 0xe9, 0x0e, 0xbd, 0x40, 0xfa, 0x29, 0xaa, 0x94, 0x4f, 0x01, 0xf4, + 0x22, 0x85, 0xfd, 0xa1, 0xe3, 0xf9, 0x26, 0x28, 0x2f, 0x33, 0x04, 0xf7, 0x5b, 0xe3, 0x48, 0xf0, + 0x61, 0xdb, 0x11, 0x8e, 0x59, 0x52, 0xfb, 0x33, 0x84, 0xdc, 0x87, 0xf5, 0x16, 0x0f, 0x84, 0x17, + 0xb0, 0x40, 0x9c, 0x04, 0xfe, 0xc4, 0x2c, 0xd7, 0x8c, 0xfa, 0x9a, 0x3d, 0x0f, 0xe2, 0x6d, 0x5b, + 0x7c, 0x1c, 0x88, 0x70, 0x22, 0x75, 0xd6, 0xa5, 0x4e, 0x12, 0xc2, 0x3c, 0x35, 0xbb, 0x72, 0x73, + 0x43, 0x6e, 0x6a, 0x09, 0xcb, 0xa8, 0xdb, 0xe3, 0x21, 0x33, 0x37, 0xe5, 0xe3, 0x28, 0x01, 0x33, + 0xde, 0x71, 0x84, 0x27, 0xc6, 0x2e, 0x33, 0x2b, 0x35, 0xa3, 0x9e, 0xb5, 0xa7, 0x32, 0xde, 0xb7, + 0xc3, 0x83, 0x81, 0xda, 0xdc, 0x92, 0x9b, 0x33, 0x60, 0x2e, 0xde, 0x16, 0x77, 0x99, 0x49, 0xe4, + 0x95, 0xe6, 0x41, 0x42, 0xa1, 0xac, 0x83, 0x43, 0x31, 0x32, 0xb7, 0xa5, 0xd2, 0x1c, 0x46, 0x1a, + 0xb0, 0xb3, 0x7f, 0xd1, 0xf3, 0xc7, 0x2e, 0x73, 0xe7, 0x74, 0x77, 0xa4, 0xee, 0xa5, 0x7b, 0x78, + 0x9b, 0x66, 0x14, 0x8c, 0x87, 0xe6, 0x8d, 0x9a, 0x51, 0x5f, 0xb7, 0x95, 0x80, 0x95, 0xd5, 0xe2, + 0xc3, 0x21, 0x0b, 0x84, 0xb9, 0xab, 0x2a, 0x4b, 0x8b, 0xb8, 0xb3, 0x1f, 0x38, 0x6f, 0x7d, 0xe6, + 0x9a, 0x7f, 0x92, 0x69, 0x89, 0x45, 0xac, 0xd8, 0xb3, 0x91, 0x69, 0x4a, 0x30, 0x7b, 0x36, 0xc2, + 0x7b, 0x69, 0x8f, 0x36, 0x73, 0x22, 0x1e, 0x98, 0x37, 0xd5, 0xbd, 0xe6, 0x40, 0xf2, 0x1c, 0xa0, + 0x2b, 0x1c, 0xc1, 0xba, 0x5e, 0xd0, 0x63, 0x66, 0xb5, 0x66, 0xd4, 0x4b, 0x8d, 0xaa, 0xa5, 0xba, + 0xde, 0x8a, 0xbb, 0xde, 0x7a, 0x13, 0x77, 0xbd, 0x9d, 0xd0, 0xc6, 0x7a, 0x6b, 0xfa, 0x3e, 0x7f, + 0x6f, 0x33, 0xd7, 0x0b, 0x59, 0x4f, 0x44, 0xe6, 0x2d, 0xf9, 0x24, 0x29, 0x94, 0xfc, 0x0b, 0xdf, + 0x26, 0x12, 0xdd, 0x49, 0xd0, 0x33, 0x6f, 0x5f, 0xeb, 0x61, 0xaa, 0x4b, 0x3e, 0x01, 0x22, 0xd7, + 0xe3, 0x5e, 0x8f, 0x45, 0x51, 0x7f, 0xec, 0x4b, 0x0b, 0x7f, 0xbe, 0xd6, 0xc2, 0x25, 0xa7, 0xc8, + 0x0b, 0x28, 0x21, 0x7a, 0xcc, 0x5d, 0xd4, 0x33, 0xef, 0x5c, 0x6b, 0x24, 0xa9, 0x4e, 0x9f, 0xc2, + 0xa6, 0xe2, 0x85, 0x8e, 0x17, 0x09, 0xc5, 0x73, 0xf7, 0xa0, 0xa0, 0xa0, 0xc8, 0x34, 0x6a, 0xb9, + 0x7a, 0xa9, 0x51, 0xb0, 0x94, 0x6c, 0xc7, 0x38, 0xb5, 0x60, 0x4d, 0x2d, 0x8f, 0xda, 0x1f, 0xc2, + 0x27, 0xf4, 0x09, 0x80, 0x26, 0x2a, 0x74, 0xf0, 0x97, 0xb4, 0x83, 0xa2, 0x15, 0x5b, 0x9b, 0xb9, + 0x78, 0x09, 0xdb, 0xad, 0x73, 0x27, 0x18, 0x30, 0x7c, 0x96, 0x71, 0x14, 0x53, 0x5c, 0xda, 0x5b, + 0xa2, 0x6a, 0xb2, 0x73, 0x55, 0x43, 0xef, 0xc5, 0x37, 0x3b, 0x6a, 0x5f, 0x71, 0x98, 0xfe, 0x62, + 0xc0, 0x46, 0xd3, 0x75, 0xf5, 0xed, 0x64, 0x6c, 0xc9, 0x6e, 0x33, 0x96, 0x75, 0x5b, 0x36, 0xdd, + 0x6d, 0xb2, 0xb2, 0x65, 0xfd, 0xc7, 0x9c, 0xa9, 0x45, 0x3c, 0x37, 0x6d, 0x39, 0x4d, 0x9a, 0x33, + 0x80, 0x54, 0x20, 0xd7, 0xec, 0xbe, 0xd6, 0x94, 0x89, 0x4b, 0x8c, 0xe1, 0x73, 0x27, 0x0c, 0xbc, + 0x60, 0x80, 0xa4, 0x9f, 0x43, 0x8e, 0x8d, 0x65, 0xfa, 0x10, 0xb6, 0xce, 0x46, 0xae, 0x23, 0x58, + 0x32, 0x68, 0x02, 0x2b, 0x6d, 0xaf, 0xdf, 0xd7, 0xa4, 0x2f, 0xd7, 0x74, 0x00, 0x3b, 0x87, 0x8c, + 0x2f, 0xea, 0xde, 0x8d, 0x07, 0x81, 0xd4, 0x4e, 0x3c, 0x6e, 0x3c, 0x1f, 0x62, 0x63, 0xd9, 0x99, + 0xb1, 0xb9, 0x88, 0x72, 0xa9, 0x88, 0x1a, 0x60, 0xda, 0xac, 0x1f, 0xb2, 0x08, 0x5f, 0x97, 0x47, + 0x9e, 0xe0, 0xe1, 0x24, 0x4e, 0xf8, 0x2e, 0xe4, 0x6d, 0x76, 0xee, 0x44, 0xe7, 0xd2, 0xd9, 0x9a, + 0xad, 0x25, 0xfa, 0xa3, 0x01, 0x5b, 0xdd, 0x9e, 0x13, 0xc4, 0x81, 0x5d, 0xfe, 0xb6, 0xc8, 0xd7, + 0x63, 0xc1, 0xd5, 0x83, 0xea, 0xe7, 0x4d, 0x20, 0xe4, 0x19, 0xac, 0x9d, 0x62, 0x79, 0xf7, 0xb8, + 0x2f, 0x53, 0xbe, 0xd1, 0xb8, 0x69, 0x2d, 0x58, 0xb5, 0x8e, 0x99, 0x38, 0xe7, 0xae, 0x3d, 0x55, + 0xa5, 0x7f, 0x85, 0xbc, 0xc2, 0x48, 0x01, 0x72, 0xcd, 0x4e, 0xa7, 0x92, 0xc1, 0xc5, 0xc1, 0x9b, + 0xd3, 0x8a, 0x41, 0x8a, 0xb0, 0x6a, 0x77, 0xbf, 0x78, 0xdd, 0xaa, 0x64, 0xe9, 0xcf, 0x06, 0x6c, + 0x26, 0xad, 0xe9, 0x4f, 0x80, 0xb8, 0xda, 0x8c, 0x79, 0x8e, 0xa2, 0x50, 0x3e, 0xf0, 0x7c, 0x16, + 0x1d, 0x05, 0x2e, 0xbb, 0xd0, 0xc5, 0x98, 0xb3, 0xe7, 0x30, 0xd4, 0xf9, 0x34, 0xe0, 0xef, 0x83, + 0x58, 0x27, 0xa7, 0x74, 0x92, 0x18, 0x7a, 0xb0, 0xd9, 0x90, 0xbf, 0x63, 0xae, 0xac, 0x94, 0x9c, + 0x1d, 0x8b, 0x98, 0x8d, 0x37, 0x5f, 0x9e, 0xf4, 0xfb, 0x11, 0x13, 0xc7, 0x91, 0x2c, 0x97, 0x9c, + 0x9d, 0x40, 0xe8, 0xf7, 0x06, 0x54, 0xb0, 0x57, 0x22, 0xf4, 0x79, 0xed, 0x17, 0x01, 0xd9, 0x83, + 0x62, 0x1b, 0xf9, 0x4e, 0x38, 0xa1, 0x90, 0xd1, 0x2e, 0x27, 0x8d, 0x99, 0x32, 0x79, 0x0a, 0x05, + 0x14, 0xf6, 0x03, 0x75, 0x83, 0xe5, 0xe7, 0x62, 0x55, 0xfa, 0x0d, 0x6c, 0x24, 0xa2, 0xc3, 0x64, + 0xfe, 0x03, 0x56, 0xfb, 0x98, 0x1e, 0x4d, 0x02, 0x55, 0x6b, 0x7e, 0xdf, 0x92, 0xb9, 0xdb, 0xc7, + 0x0e, 0xb2, 0x95, 0x62, 0x75, 0x0f, 0x60, 0x06, 0x62, 0xe3, 0x7c, 0xc5, 0x26, 0xfa, 0x5e, 0xb8, + 0xc4, 0x91, 0xf3, 0xce, 0xf1, 0xc7, 0x4c, 0x67, 0x5f, 0x09, 0xcf, 0xb3, 0x7b, 0x06, 0xfd, 0xd6, + 0x00, 0x22, 0xcd, 0x2f, 0xaf, 0xb8, 0x3f, 0x3a, 0x29, 0x4c, 0x3f, 0xd9, 0x6f, 0x6a, 0x50, 0xfc, + 0x04, 0x53, 0xf1, 0x47, 0xfa, 0xa2, 0x53, 0x59, 0x7e, 0x89, 0x4e, 0x04, 0x8b, 0x74, 0x6d, 0x29, + 0x81, 0x1e, 0x20, 0x17, 0x08, 0xcd, 0xf3, 0x7c, 0x10, 0x2d, 0x69, 0xb8, 0x63, 0xe7, 0xc2, 0x66, + 0xd1, 0xd8, 0xd7, 0xb6, 0x57, 0xed, 0x04, 0x42, 0xeb, 0x40, 0x52, 0x76, 0x34, 0xfb, 0xf8, 0x5e, + 0xc0, 0xe4, 0x33, 0x16, 0x6d, 0xb9, 0xa6, 0xf7, 0x65, 0x8f, 0x85, 0x5e, 0x0f, 0xa3, 0xc5, 0x37, + 0x0b, 0x70, 0x24, 0xa8, 0xa7, 0x9a, 0xca, 0xf4, 0x11, 0x94, 0x94, 0x56, 0x84, 0xd3, 0x27, 0xa5, + 0x9a, 0x9b, 0x53, 0xad, 0x41, 0x5e, 0x0d, 0x02, 0xe4, 0x14, 0xb5, 0x8a, 0x39, 0x45, 0x49, 0x8d, + 0x9f, 0x00, 0x72, 0xad, 0xce, 0x11, 0x79, 0x06, 0x70, 0xc8, 0x44, 0xfc, 0x99, 0xbd, 0xbb, 0xf0, + 0x0c, 0xfb, 0xf8, 0x23, 0xa0, 0xba, 0x6e, 0x25, 0xbf, 0xed, 0x69, 0x86, 0xfc, 0x07, 0x0a, 0x67, + 0xa3, 0x41, 0xe8, 0xb8, 0xec, 0xca, 0x33, 0x57, 0xe0, 0x34, 0x43, 0x9e, 0x23, 0xcf, 0xf9, 0xdc, + 0x71, 0x3f, 0xe2, 0xec, 0xff, 0xa0, 0x9c, 0x1c, 0x74, 0x64, 0xc7, 0xba, 0x64, 0xee, 0x2d, 0x39, + 0xdf, 0x80, 0x15, 0x99, 0xbd, 0xab, 0x3c, 0x57, 0xac, 0xd4, 0x80, 0xa7, 0x19, 0xf2, 0x08, 0x40, + 0xcf, 0xc6, 0xa0, 0xcf, 0x49, 0xc5, 0x4a, 0x0d, 0xca, 0x6a, 0x5c, 0x73, 0x34, 0x43, 0x1e, 0xe2, + 0x27, 0xb5, 0x1e, 0x91, 0x24, 0xc6, 0xab, 0x9b, 0xd6, 0xfc, 0xdc, 0xa4, 0x19, 0xf2, 0x77, 0x28, + 0x27, 0xa7, 0xcd, 0x4c, 0x97, 0x58, 0x0b, 0x53, 0x48, 0xa6, 0xac, 0xac, 0x98, 0x4d, 0xab, 0x2f, + 0x06, 0x71, 0xf5, 0x95, 0x5f, 0xc0, 0x66, 0x6a, 0xb6, 0x5d, 0x72, 0xfc, 0x86, 0x75, 0xd9, 0xfc, + 0xa3, 0x19, 0xf2, 0x0a, 0xb6, 0x16, 0x06, 0x16, 0xb9, 0x69, 0x5d, 0x35, 0xc4, 0x96, 0xc4, 0xf1, + 0x14, 0x60, 0x36, 0x21, 0x08, 0x59, 0x1c, 0x3e, 0xd5, 0x8a, 0x95, 0x1a, 0x21, 0x34, 0x43, 0x9e, + 0x40, 0x71, 0xca, 0x74, 0x64, 0xcb, 0x4a, 0x73, 0x76, 0x75, 0x33, 0x45, 0x84, 0x34, 0x43, 0xfe, + 0x0d, 0xa5, 0x04, 0x4f, 0x90, 0x6d, 0x6b, 0x91, 0xcb, 0xaa, 0x5b, 0x56, 0x9a, 0x4a, 0x68, 0x86, + 0xec, 0xc1, 0xca, 0xa9, 0x17, 0x0c, 0x3e, 0xa2, 0x2c, 0xff, 0x0b, 0xeb, 0x73, 0xbd, 0x4e, 0x30, + 0x9f, 0x8b, 0x1c, 0x52, 0xdd, 0xb6, 0x16, 0x29, 0x81, 0x66, 0x88, 0xa5, 0xca, 0x46, 0x71, 0x40, + 0xc1, 0x52, 0x8b, 0x25, 0xee, 0x2c, 0x28, 0xb6, 0x99, 0xff, 0xe1, 0xfa, 0x0f, 0xa0, 0x84, 0x05, + 0xad, 0xe9, 0x63, 0x76, 0xa2, 0x6c, 0x25, 0x18, 0x85, 0x66, 0xc8, 0x33, 0xa8, 0x1c, 0x32, 0xa1, + 0x7a, 0x29, 0x56, 0xbe, 0x2a, 0x19, 0x05, 0x4b, 0xe9, 0xc9, 0xa6, 0x04, 0x35, 0xd9, 0xf1, 0x73, + 0xe3, 0x23, 0xb2, 0xf7, 0x12, 0x4a, 0x6d, 0x2f, 0xfa, 0x1d, 0x06, 0x1a, 0x32, 0xfd, 0x2a, 0x9e, + 0xa5, 0x26, 0x12, 0x41, 0xff, 0x0d, 0x4a, 0xf2, 0x2b, 0x5b, 0x57, 0xc9, 0xba, 0x95, 0xfc, 0x73, + 0xa0, 0x5a, 0xb2, 0x66, 0x9f, 0xe0, 0x34, 0xf3, 0x36, 0x2f, 0xed, 0xfc, 0xf3, 0xd7, 0x00, 0x00, + 0x00, 0xff, 0xff, 0x50, 0xee, 0xcf, 0xe7, 0x30, 0x11, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1434,6 +1561,13 @@ type CLIClient interface { StatsMirror(ctx context.Context, in *StatsMirrorRequest, opts ...grpc.CallOption) (*StatsMirrorReply, error) Ping(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*empty.Empty, error) GetMirrorLogs(ctx context.Context, in *GetMirrorLogsRequest, opts ...grpc.CallOption) (*GetMirrorLogsReply, error) + AddMetric(ctx context.Context, in *Metric, opts ...grpc.CallOption) (*empty.Empty, error) + DelMetric(ctx context.Context, in *Metric, opts ...grpc.CallOption) (*empty.Empty, error) + ListMetrics(ctx context.Context, in *Metric, opts ...grpc.CallOption) (*MetricsList, error) + GetStatusMetrics(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*Status, error) + EnableAuto(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*empty.Empty, error) + DisableAuto(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*empty.Empty, error) + GetStatusAuto(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*Status, error) // Tools MatchMirror(ctx context.Context, in *MatchRequest, opts ...grpc.CallOption) (*MatchReply, error) } @@ -1590,6 +1724,69 @@ func (c *cLIClient) GetMirrorLogs(ctx context.Context, in *GetMirrorLogsRequest, return out, nil } +func (c *cLIClient) AddMetric(ctx context.Context, in *Metric, opts ...grpc.CallOption) (*empty.Empty, error) { + out := new(empty.Empty) + err := c.cc.Invoke(ctx, "/CLI/AddMetric", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *cLIClient) DelMetric(ctx context.Context, in *Metric, opts ...grpc.CallOption) (*empty.Empty, error) { + out := new(empty.Empty) + err := c.cc.Invoke(ctx, "/CLI/DelMetric", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *cLIClient) ListMetrics(ctx context.Context, in *Metric, opts ...grpc.CallOption) (*MetricsList, error) { + out := new(MetricsList) + err := c.cc.Invoke(ctx, "/CLI/ListMetrics", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *cLIClient) GetStatusMetrics(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*Status, error) { + out := new(Status) + err := c.cc.Invoke(ctx, "/CLI/GetStatusMetrics", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *cLIClient) EnableAuto(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*empty.Empty, error) { + out := new(empty.Empty) + err := c.cc.Invoke(ctx, "/CLI/EnableAuto", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *cLIClient) DisableAuto(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*empty.Empty, error) { + out := new(empty.Empty) + err := c.cc.Invoke(ctx, "/CLI/DisableAuto", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *cLIClient) GetStatusAuto(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*Status, error) { + out := new(Status) + err := c.cc.Invoke(ctx, "/CLI/GetStatusAuto", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *cLIClient) MatchMirror(ctx context.Context, in *MatchRequest, opts ...grpc.CallOption) (*MatchReply, error) { out := new(MatchReply) err := c.cc.Invoke(ctx, "/CLI/MatchMirror", in, out, opts...) @@ -1617,6 +1814,13 @@ type CLIServer interface { StatsMirror(context.Context, *StatsMirrorRequest) (*StatsMirrorReply, error) Ping(context.Context, *empty.Empty) (*empty.Empty, error) GetMirrorLogs(context.Context, *GetMirrorLogsRequest) (*GetMirrorLogsReply, error) + AddMetric(context.Context, *Metric) (*empty.Empty, error) + DelMetric(context.Context, *Metric) (*empty.Empty, error) + ListMetrics(context.Context, *Metric) (*MetricsList, error) + GetStatusMetrics(context.Context, *empty.Empty) (*Status, error) + EnableAuto(context.Context, *empty.Empty) (*empty.Empty, error) + DisableAuto(context.Context, *empty.Empty) (*empty.Empty, error) + GetStatusAuto(context.Context, *empty.Empty) (*Status, error) // Tools MatchMirror(context.Context, *MatchRequest) (*MatchReply, error) } @@ -1673,6 +1877,27 @@ func (*UnimplementedCLIServer) Ping(ctx context.Context, req *empty.Empty) (*emp func (*UnimplementedCLIServer) GetMirrorLogs(ctx context.Context, req *GetMirrorLogsRequest) (*GetMirrorLogsReply, error) { return nil, status.Errorf(codes.Unimplemented, "method GetMirrorLogs not implemented") } +func (*UnimplementedCLIServer) AddMetric(ctx context.Context, req *Metric) (*empty.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddMetric not implemented") +} +func (*UnimplementedCLIServer) DelMetric(ctx context.Context, req *Metric) (*empty.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method DelMetric not implemented") +} +func (*UnimplementedCLIServer) ListMetrics(ctx context.Context, req *Metric) (*MetricsList, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListMetrics not implemented") +} +func (*UnimplementedCLIServer) GetStatusMetrics(ctx context.Context, req *empty.Empty) (*Status, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetStatusMetrics not implemented") +} +func (*UnimplementedCLIServer) EnableAuto(ctx context.Context, req *empty.Empty) (*empty.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method EnableAuto not implemented") +} +func (*UnimplementedCLIServer) DisableAuto(ctx context.Context, req *empty.Empty) (*empty.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method DisableAuto not implemented") +} +func (*UnimplementedCLIServer) GetStatusAuto(ctx context.Context, req *empty.Empty) (*Status, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetStatusAuto not implemented") +} func (*UnimplementedCLIServer) MatchMirror(ctx context.Context, req *MatchRequest) (*MatchReply, error) { return nil, status.Errorf(codes.Unimplemented, "method MatchMirror not implemented") } @@ -1969,6 +2194,132 @@ func _CLI_GetMirrorLogs_Handler(srv interface{}, ctx context.Context, dec func(i return interceptor(ctx, in, info, handler) } +func _CLI_AddMetric_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Metric) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CLIServer).AddMetric(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/CLI/AddMetric", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CLIServer).AddMetric(ctx, req.(*Metric)) + } + return interceptor(ctx, in, info, handler) +} + +func _CLI_DelMetric_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Metric) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CLIServer).DelMetric(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/CLI/DelMetric", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CLIServer).DelMetric(ctx, req.(*Metric)) + } + return interceptor(ctx, in, info, handler) +} + +func _CLI_ListMetrics_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Metric) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CLIServer).ListMetrics(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/CLI/ListMetrics", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CLIServer).ListMetrics(ctx, req.(*Metric)) + } + return interceptor(ctx, in, info, handler) +} + +func _CLI_GetStatusMetrics_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(empty.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CLIServer).GetStatusMetrics(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/CLI/GetStatusMetrics", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CLIServer).GetStatusMetrics(ctx, req.(*empty.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _CLI_EnableAuto_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(empty.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CLIServer).EnableAuto(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/CLI/EnableAuto", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CLIServer).EnableAuto(ctx, req.(*empty.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _CLI_DisableAuto_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(empty.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CLIServer).DisableAuto(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/CLI/DisableAuto", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CLIServer).DisableAuto(ctx, req.(*empty.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _CLI_GetStatusAuto_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(empty.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CLIServer).GetStatusAuto(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/CLI/GetStatusAuto", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CLIServer).GetStatusAuto(ctx, req.(*empty.Empty)) + } + return interceptor(ctx, in, info, handler) +} + func _CLI_MatchMirror_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(MatchRequest) if err := dec(in); err != nil { @@ -2055,6 +2406,34 @@ var _CLI_serviceDesc = grpc.ServiceDesc{ MethodName: "GetMirrorLogs", Handler: _CLI_GetMirrorLogs_Handler, }, + { + MethodName: "AddMetric", + Handler: _CLI_AddMetric_Handler, + }, + { + MethodName: "DelMetric", + Handler: _CLI_DelMetric_Handler, + }, + { + MethodName: "ListMetrics", + Handler: _CLI_ListMetrics_Handler, + }, + { + MethodName: "GetStatusMetrics", + Handler: _CLI_GetStatusMetrics_Handler, + }, + { + MethodName: "EnableAuto", + Handler: _CLI_EnableAuto_Handler, + }, + { + MethodName: "DisableAuto", + Handler: _CLI_DisableAuto_Handler, + }, + { + MethodName: "GetStatusAuto", + Handler: _CLI_GetStatusAuto_Handler, + }, { MethodName: "MatchMirror", Handler: _CLI_MatchMirror_Handler, diff --git a/rpc/rpc.proto b/rpc/rpc.proto index ca75e753..169f4c6c 100644 --- a/rpc/rpc.proto +++ b/rpc/rpc.proto @@ -20,6 +20,13 @@ service CLI { rpc StatsMirror (StatsMirrorRequest) returns (StatsMirrorReply) {} rpc Ping (google.protobuf.Empty) returns (google.protobuf.Empty) {} rpc GetMirrorLogs (GetMirrorLogsRequest) returns (GetMirrorLogsReply) {} + rpc AddMetric (Metric) returns (google.protobuf.Empty) {} + rpc DelMetric (Metric) returns (google.protobuf.Empty) {} + rpc ListMetrics (Metric) returns (MetricsList) {} + rpc GetStatusMetrics (google.protobuf.Empty) returns (Status) {} + rpc EnableAuto (google.protobuf.Empty) returns (google.protobuf.Empty) {} + rpc DisableAuto (google.protobuf.Empty) returns (google.protobuf.Empty) {} + rpc GetStatusAuto (google.protobuf.Empty) returns (Status) {} // Tools rpc MatchMirror (MatchRequest) returns (MatchReply) {} @@ -165,3 +172,15 @@ message GetMirrorLogsRequest { message GetMirrorLogsReply { repeated string line = 1; } + +message Metric { + string Filename = 1; +} + +message MetricsList { + repeated string Filename = 1; +} + +message Status { + bool Status = 1; +} diff --git a/scan/scan.go b/scan/scan.go index 06fb2c05..d0d45734 100644 --- a/scan/scan.go +++ b/scan/scan.go @@ -236,6 +236,11 @@ func (s *scan) ScannerAddFile(f filedata) { ik := fmt.Sprintf("FILEINFO_%d_%s", s.mirrorid, f.path) s.conn.Send("HMSET", ik, "size", f.size, "modTime", f.modTime) + // Add file to tracked files if auto tracked files is enabled + if GetConfig().MetricsAutoTrackedFiles { + s.conn.Send("SADD", "TRACKED_FILES", f.path) + } + // Publish update database.SendPublish(s.conn, database.MIRROR_FILE_UPDATE, fmt.Sprintf("%d %s", s.mirrorid, f.path)) }