Help us shape the future of Go! Take the 2026 Go Developer Survey

clisd

package
v1.3.101 Latest Latest
Warning

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

Go to latest
Published: Oct 13, 2026 License: AGPL-3.0 Imports: 17 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.

Use --testenv or SKYWIRETEST=1 to use test deployment services.`,
		getDeployment().ServiceDiscovery,
		getDeployment().TransportDiscovery),
	Run: func(cmd *cobra.Command, _ []string) {

		if testEnv && !isTestEnv() {

			if !cmd.Flags().Changed("sdurl") {
				sdURL = deployment.Test.ServiceDiscovery
			}
			if !cmd.Flags().Changed("tpdurl") {
				tpdURL = deployment.Test.TransportDiscovery
			}
			if !cmd.Flags().Changed("uturl") {
				utURL = deployment.Test.TransportDiscovery
			}
			if !cmd.Flags().Changed("cds") {
				cacheDirSD = cacheDirPath(deployment.Test.ServiceDiscovery)
			}
			if !cmd.Flags().Changed("cdt") {
				cacheDirTPD = cacheDirPath(deployment.Test.TransportDiscovery)
			}
			if !cmd.Flags().Changed("cdu") {
				cacheDirUT = cacheDirPath(deployment.Test.TransportDiscovery)
			}
		}

		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.Split(e.Address, ":")[0]
			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.Split(e.Address, ":")[0]
			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.Split(e.Address, ":")[0]
			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],
			})
		}

		sort.Slice(entries, func(i, j int) bool {
			return entries[i].Total > entries[j].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