clisd

package
v1.3.106 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 4, 2026 License: AGPL-3.0 Imports: 18 Imported by: 0

Documentation

Overview

Package clisd cmd/skywire-cli/commands/sd/root.go c4-vis-cli

Package clisd cmd/skywire-cli/commands/sd/transport_counts.go c4-vis-cli

Index

Constants

This section is empty.

Variables

View Source
var RootCmd = &cobra.Command{
	Use:   "sd",
	Short: "Service discovery network statistics",
	Long: fmt.Sprintf(`Display combined service discovery and transport statistics.

Combines data from:
- Service Discovery: %v/api/services (proxy / vpn / visor entries)
- Transport Discovery: %v/all-transports (transport counts by type)
- Uptime Tracker: online/offline status (color-coded rows)

Shows public keys with their advertised services and transport counts by
type (stcpr/squicr/sudph/stcp/webrtc/swsr/swtr/dmsg). Filter with --country, --version, --min
(minimum transport count); --noton keeps offline/not-in-UT visors. --json
emits the combined rows as machine-readable output.`,
		getDeployment().ServiceDiscovery,
		getDeployment().TransportDiscovery),
	Run: func(cmd *cobra.Command, _ []string) {

		sdProxyURL := sdURL + "/api/services?type=" + servicedisc.ServiceTypeProxy
		sdVpnURL := sdURL + "/api/services?type=" + servicedisc.ServiceTypeVPN
		sdVisorURL := sdURL + "/api/services?type=" + servicedisc.ServiceTypeVisor
		tpdFullURL := tpdURL + "/all-transports"
		utFullURL := clirpc.IntegratedUptimeURLDays(utURL, 1)

		proxyData := clirpc.FetchCachedServiceURL(cmd.Flags(), cacheFile(cacheDirSD, sdProxyURL), sdProxyURL, cacheFilesAge)
		vpnData := clirpc.FetchCachedServiceURL(cmd.Flags(), cacheFile(cacheDirSD, sdVpnURL), sdVpnURL, cacheFilesAge)
		visorData := clirpc.FetchCachedServiceURL(cmd.Flags(), cacheFile(cacheDirSD, sdVisorURL), sdVisorURL, cacheFilesAge)

		tpdData := clirpc.FetchCachedServiceURL(cmd.Flags(), cacheFile(cacheDirTPD, tpdFullURL), tpdFullURL, cacheFilesAge)

		// Parse service discovery data
		type sdEntry struct {
			Address string `json:"address"`
			Geo     struct {
				Country string `json:"country"`
			} `json:"geo"`
			Version string `json:"version"`
		}

		var proxyEntries, vpnEntries, visorEntries []sdEntry
		json.Unmarshal([]byte(proxyData), &proxyEntries)
		json.Unmarshal([]byte(vpnData), &vpnEntries)
		json.Unmarshal([]byte(visorData), &visorEntries)

		// Build service map by public key
		type serviceInfo struct {
			PK        string
			DisplayPK string
			Country   string
			Version   string
			Services  []string
		}
		serviceMap := make(map[string]*serviceInfo)

		for _, e := range proxyEntries {
			pk, _, _ := strings.Cut(e.Address, ":")
			if serviceMap[pk] == nil {
				serviceMap[pk] = &serviceInfo{PK: pk, Country: e.Geo.Country, Version: e.Version}
			}
			serviceMap[pk].Services = append(serviceMap[pk].Services, "proxy")
		}

		for _, e := range vpnEntries {
			pk, _, _ := strings.Cut(e.Address, ":")
			if serviceMap[pk] == nil {
				serviceMap[pk] = &serviceInfo{PK: pk, Country: e.Geo.Country, Version: e.Version}
			}
			serviceMap[pk].Services = append(serviceMap[pk].Services, "vpn")
			if serviceMap[pk].Country == "" {
				serviceMap[pk].Country = e.Geo.Country
			}
			if serviceMap[pk].Version == "" {
				serviceMap[pk].Version = e.Version
			}
		}

		for _, e := range visorEntries {
			pk, _, _ := strings.Cut(e.Address, ":")
			if serviceMap[pk] == nil {
				serviceMap[pk] = &serviceInfo{PK: pk, DisplayPK: e.Address, Country: e.Geo.Country, Version: e.Version}
			} else if serviceMap[pk].DisplayPK == "" {
				serviceMap[pk].DisplayPK = e.Address
			}
			serviceMap[pk].Services = append(serviceMap[pk].Services, "visor")
			if serviceMap[pk].Country == "" {
				serviceMap[pk].Country = e.Geo.Country
			}
			if serviceMap[pk].Version == "" {
				serviceMap[pk].Version = e.Version
			}
		}

		utData := clirpc.FetchCachedServiceURL(cmd.Flags(), cacheFile(cacheDirUT, utFullURL), utFullURL, cacheFilesAge)

		type utEntry struct {
			PK string `json:"pk"`
			On bool   `json:"on"`
		}

		var utEntries []utEntry
		json.Unmarshal([]byte(utData), &utEntries)

		utStatus := make(map[string]string)
		for _, ut := range utEntries {
			if ut.On {
				utStatus[ut.PK] = "online"
			} else {
				utStatus[ut.PK] = "offline"
			}
		}

		if !noFilterOnline {
			for pk := range serviceMap {
				if utStatus[pk] != "online" {
					delete(serviceMap, pk)
				}
			}
		}

		// Parse transport data and count transports per key
		type tpEntry struct {
			Edges []string `json:"edges"`
			Type  string   `json:"type"`
		}

		var tpEntries []tpEntry
		json.Unmarshal([]byte(tpdData), &tpEntries)

		tpCountMap := make(map[string]*transportCounts)
		other := false

		for _, tp := range tpEntries {
			for _, edge := range tp.Edges {
				if tpCountMap[edge] == nil {
					tpCountMap[edge] = &transportCounts{}
				}
				if !tpCountMap[edge].add(tptypes.Type(tp.Type)) {
					other = true
				}
			}
		}

		// Combine data
		type networkEntry struct {
			PK       string `json:"pk"`
			Country  string `json:"country,omitempty"`
			Version  string `json:"version,omitempty"`
			Services string `json:"services,omitempty"`
			transportCounts
			UTStatus string `json:"ut_status,omitempty"` // "online", "offline", or "" (not in UT)
		}

		var entries []networkEntry

		for pk, info := range serviceMap {
			services := strings.Join(info.Services, ",")
			counts := tpCountMap[pk]
			if counts == nil {
				counts = &transportCounts{}
			}

			if country != "" && info.Country != country {
				continue
			}
			if version != "" && info.Version != version {
				continue
			}
			if minTransports > 0 && counts.Total < minTransports {
				continue
			}

			displayPK := pk
			if info.DisplayPK != "" {
				displayPK = info.DisplayPK
			}

			entries = append(entries, networkEntry{
				PK:              displayPK,
				Country:         info.Country,
				Version:         info.Version,
				Services:        services,
				transportCounts: *counts,
				UTStatus:        utStatus[pk],
			})
		}

		for pk, counts := range tpCountMap {
			if serviceMap[pk] != nil {
				continue
			}

			if !noFilterOnline && utStatus[pk] != "online" {
				continue
			}

			if minTransports > 0 && counts.Total < minTransports {
				continue
			}

			entries = append(entries, networkEntry{
				PK:              pk,
				transportCounts: *counts,
				UTStatus:        utStatus[pk],
			})
		}

		slices.SortFunc(entries, func(a, b networkEntry) int {
			return cmp.Compare(b.Total, a.Total)
		})

		if jsonOutput {
			internal.PrintOutput(cmd.Flags(), entries, "")
			return
		}

		cmdutil.Printf("Legend: %s %s %s\n\n",
			pterm.Red("*offline"),
			pterm.BgRed.Sprint(pterm.White("*not in UT")),
			pterm.BgRed.Sprint(pterm.Black("*online <2 transports")))

		// Use tabwriter for alignment
		var b bytes.Buffer
		w := tabwriter.NewWriter(&b, 0, 0, 3, ' ', tabwriter.TabIndent)

		fmt.Fprintln(w, "pk\tcountry\tversion\tservices\t"+transportColumns(other)+"\ttotal")

		for _, e := range entries {
			fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%d\n",
				e.PK, e.Country, e.Version, e.Services, e.row(other), e.Total)
		}

		w.Flush()

		lines := strings.Split(b.String(), "\n")
		for i, line := range lines {
			if line == "" {
				continue
			}

			if i == 0 {
				cmdutil.Println(line)
				continue
			}

			entryIdx := i - 1
			if entryIdx >= len(entries) {
				cmdutil.Println(line)
				continue
			}

			e := entries[entryIdx]
			realTransports := e.direct()

			switch {
			case e.UTStatus == "":

				cmdutil.Println(pterm.BgRed.Sprint(pterm.White(line)))
			case e.UTStatus == "offline":

				cmdutil.Println(pterm.Red(line))
			case e.UTStatus == "online" && realTransports < 2:

				cmdutil.Println(pterm.BgRed.Sprint(pterm.Black(line)))
			default:

				cmdutil.Println(line)
			}
		}
	},
}

RootCmd contains commands that interact with service discovery

Functions

This section is empty.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL