v2.31.1 f37fe67

traffic_status.go RawHistoryFolder

1package cli
2
3import (
4	"context"
5	"fmt"
6	"io"
7	"net"
8
9	"github.com/spf13/cobra"
10
11	"github.com/bnema/gordon/internal/adapters/dto"
12)
13
14var trafficResolveControlPlane = resolveControlPlane
15
16func newTrafficStatusCmd() *cobra.Command {
17	var jsonOut bool
18	cmd := &cobra.Command{
19		Use:   "status",
20		Short: "Show traffic entrypoint, router, and counter status",
21		Args:  cobra.NoArgs,
22		RunE: func(cmd *cobra.Command, args []string) error {
23			handle, err := trafficResolveControlPlane(configPath)
24			if err != nil {
25				return err
26			}
27			defer handle.close()
28			return runTrafficStatus(cmd.Context(), handle.plane, cmd.OutOrStdout(), jsonOut)
29		},
30	}
31	cmd.Flags().BoolVar(&jsonOut, "json", false, "Output JSON")
32	return cmd
33}
34
35func runTrafficStatus(ctx context.Context, cp ControlPlane, out io.Writer, jsonOut bool) error {
36	status, err := cp.GetTrafficStatus(ctx)
37	if err != nil {
38		return fmt.Errorf("fetch traffic status: %w", err)
39	}
40	if err := renderTrafficStatus(out, status, jsonOut); err != nil {
41		return fmt.Errorf("render traffic status: %w", err)
42	}
43	return nil
44}
45
46func renderTrafficStatus(out io.Writer, status *dto.TrafficStatusResponse, jsonOut bool) error {
47	if jsonOut {
48		return writeJSON(out, status)
49	}
50	if err := renderTrafficStatusHeader(out, status); err != nil {
51		return err
52	}
53	if err := renderTrafficEntryPoints(out, status.EntryPoints); err != nil {
54		return err
55	}
56	if err := renderTrafficRouters(out, status.Routers); err != nil {
57		return err
58	}
59	if err := renderTrafficServices(out, status.Services); err != nil {
60		return err
61	}
62	return renderTrafficTotals(out, status.Counters)
63}
64
65func renderTrafficStatusHeader(out io.Writer, status *dto.TrafficStatusResponse) error {
66	if err := cliWriteLine(out, cliRenderTitle("Traffic Status")); err != nil {
67		return err
68	}
69	if err := cliWriteLine(out, cliRenderMeta("Reload", status.LastReloadStatus)); err != nil {
70		return err
71	}
72	if status.LastReloadError == "" {
73		return nil
74	}
75	return cliWriteLine(out, cliRenderWarning("Last reload error: "+status.LastReloadError))
76}
77
78func renderTrafficEntryPoints(out io.Writer, entries []dto.TrafficEntryPointStatus) error {
79	if err := cliWriteLine(out, ""); err != nil {
80		return err
81	}
82	if err := cliWriteLine(out, "Entrypoints:"); err != nil {
83		return err
84	}
85	if len(entries) == 0 {
86		return cliWriteLine(out, cliRenderMuted("  none"))
87	}
88	for _, entry := range entries {
89		if err := renderTrafficEntryPoint(out, entry); err != nil {
90			return err
91		}
92	}
93	return nil
94}
95
96func renderTrafficEntryPoint(out io.Writer, entry dto.TrafficEntryPointStatus) error {
97	if err := cliWritef(out, "  %s  %s  %s  active=%t tcp=%d udp=%d accepted=%d refused=%d errors=%d\n",
98		entry.Name, entry.Protocol, entry.Address, entry.Active, entry.ActiveTCPConnections, entry.ActiveUDPSessions,
99		entry.TotalAccepted, entry.TotalRefused, entry.TotalErrors); err != nil {
100		return err
101	}
102	if hasSmartTCPCounters(entry.SmartTCP) {
103		return renderSmartTCPCounters(out, "    smart_tcp", entry.SmartTCP)
104	}
105	return nil
106}
107
108func renderTrafficTotals(out io.Writer, counters dto.TrafficCounters) error {
109	if err := cliWriteLine(out, ""); err != nil {
110		return err
111	}
112	if err := cliWritef(out, "Totals: tcp=%d udp=%d accepted=%d refused=%d errors=%d bytes_in=%d bytes_out=%d\n",
113		counters.ActiveTCPConnections, counters.ActiveUDPSessions, counters.TotalAccepted,
114		counters.TotalRefused, counters.TotalErrors, counters.BytesIn, counters.BytesOut); err != nil {
115		return err
116	}
117	if hasSmartTCPCounters(counters.SmartTCP) {
118		return renderSmartTCPCounters(out, "Smart TCP totals", counters.SmartTCP)
119	}
120	return nil
121}
122
123func hasSmartTCPCounters(c dto.SmartTCPCounters) bool {
124	return c.HTTPAccepted != 0 || c.H2CAccepted != 0 || c.HTTPSFallbackAccepted != 0 || c.TLSPassthroughAccepted != 0 ||
125		c.RawFallbackAccepted != 0 || c.EntrypointCIDRRefused != 0 || c.RawFallbackCIDRRefused != 0 || c.PROXYRefused != 0 ||
126		c.UnknownNoFallbackRefused != 0 || c.MalformedRejected != 0 || c.SniffTimeout != 0 || c.ClientHelloTooLarge != 0
127}
128
129func renderSmartTCPCounters(out io.Writer, label string, c dto.SmartTCPCounters) error {
130	return cliWritef(out, "%s: http_accepted=%d h2c_accepted=%d https_fallback_accepted=%d tls_passthrough_accepted=%d raw_fallback_accepted=%d entrypoint_cidr_refused=%d raw_fallback_cidr_refused=%d proxy_refused=%d unknown_no_fallback_refused=%d malformed_rejected=%d sniff_timeout=%d client_hello_too_large=%d\n",
131		label, c.HTTPAccepted, c.H2CAccepted, c.HTTPSFallbackAccepted, c.TLSPassthroughAccepted, c.RawFallbackAccepted,
132		c.EntrypointCIDRRefused, c.RawFallbackCIDRRefused, c.PROXYRefused, c.UnknownNoFallbackRefused, c.MalformedRejected,
133		c.SniffTimeout, c.ClientHelloTooLarge)
134}
135
136func renderTrafficRouters(out io.Writer, routers []dto.TrafficRouterStatus) error {
137	if err := cliWriteLine(out, ""); err != nil {
138		return err
139	}
140	if err := cliWriteLine(out, "Routers:"); err != nil {
141		return err
142	}
143	if len(routers) == 0 {
144		return cliWriteLine(out, cliRenderMuted("  none"))
145	}
146	for _, router := range routers {
147		rule := router.Rule.Host
148		if rule == "" {
149			rule = router.Rule.SNI
150		}
151		if err := cliWritef(out, "  %s  %s  entrypoint=%s rule=%s service=%s active=%t\n",
152			router.Name, router.Protocol, router.EntryPoint, rule, router.Service, router.Active); err != nil {
153			return err
154		}
155	}
156	return nil
157}
158
159func renderTrafficServices(out io.Writer, services []dto.TrafficServiceStatus) error {
160	if err := cliWriteLine(out, ""); err != nil {
161		return err
162	}
163	if err := cliWriteLine(out, "Services:"); err != nil {
164		return err
165	}
166	if len(services) == 0 {
167		return cliWriteLine(out, cliRenderMuted("  none"))
168	}
169	for _, service := range services {
170		if err := cliWritef(out, "  %s  active=%t\n", service.Name, service.Active); err != nil {
171			return err
172		}
173		for _, backend := range service.Backends {
174			addr := net.JoinHostPort(backend.Host, fmt.Sprint(backend.Port))
175			if err := cliWritef(out, "    %s  %s://%s active=%t\n",
176				backend.Name, backend.Protocol, addr, backend.Active); err != nil {
177				return err
178			}
179		}
180	}
181	return nil
182}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close