Skip to content

Commit b3304bf

Browse files
committed
TUN-8727: implement metrics, runtime, system, and tunnelstate in diagnostic http client
## Summary The diagnostic procedure needs to extract information available in the metrics server via HTTP calls. These changes add to the diagnostic client the remaining endpoints. Closes TUN-8727
1 parent 28796c6 commit b3304bf

File tree

6 files changed

+108
-26
lines changed

6 files changed

+108
-26
lines changed

diagnostic/client.go

Lines changed: 88 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,20 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"io"
78
"net/http"
89
"net/url"
910
"strconv"
1011

1112
"github.com/cloudflare/cloudflared/logger"
1213
)
1314

14-
const configurationEndpoint = "diag/configuration"
15-
1615
type httpClient struct {
1716
http.Client
18-
baseURL url.URL
17+
baseURL *url.URL
1918
}
2019

21-
func NewHTTPClient(baseURL url.URL) *httpClient {
20+
func NewHTTPClient() *httpClient {
2221
httpTransport := http.Transport{
2322
TLSHandshakeTimeout: defaultTimeout,
2423
ResponseHeaderTimeout: defaultTimeout,
@@ -29,12 +28,21 @@ func NewHTTPClient(baseURL url.URL) *httpClient {
2928
Transport: &httpTransport,
3029
Timeout: defaultTimeout,
3130
},
32-
baseURL,
31+
nil,
3332
}
3433
}
3534

36-
func (client *httpClient) GET(ctx context.Context, url string) (*http.Response, error) {
37-
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
35+
func (client *httpClient) SetBaseURL(baseURL *url.URL) {
36+
client.baseURL = baseURL
37+
}
38+
39+
func (client *httpClient) GET(ctx context.Context, endpoint string) (*http.Response, error) {
40+
if client.baseURL == nil {
41+
return nil, ErrNoBaseUrl
42+
}
43+
url := client.baseURL.JoinPath(endpoint)
44+
45+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url.String(), nil)
3846
if err != nil {
3947
return nil, fmt.Errorf("error creating GET request: %w", err)
4048
}
@@ -56,12 +64,7 @@ type LogConfiguration struct {
5664
}
5765

5866
func (client *httpClient) GetLogConfiguration(ctx context.Context) (*LogConfiguration, error) {
59-
endpoint, err := url.JoinPath(client.baseURL.String(), configurationEndpoint)
60-
if err != nil {
61-
return nil, fmt.Errorf("error parsing URL: %w", err)
62-
}
63-
64-
response, err := client.GET(ctx, endpoint)
67+
response, err := client.GET(ctx, configurationEndpoint)
6568
if err != nil {
6669
return nil, err
6770
}
@@ -93,9 +96,79 @@ func (client *httpClient) GetLogConfiguration(ctx context.Context) (*LogConfigur
9396
return &LogConfiguration{"", logDirectory, uid}, nil
9497
}
9598

96-
return nil, ErrKeyNotFound
99+
// No log configured may happen when cloudflared is executed as a managed service or
100+
// when containerized
101+
return &LogConfiguration{"", "", uid}, nil
102+
}
103+
104+
func (client *httpClient) GetMemoryDump(ctx context.Context, writer io.Writer) error {
105+
response, err := client.GET(ctx, memoryDumpEndpoint)
106+
if err != nil {
107+
return err
108+
}
109+
110+
return copyToWriter(response, writer)
111+
}
112+
113+
func (client *httpClient) GetGoroutineDump(ctx context.Context, writer io.Writer) error {
114+
response, err := client.GET(ctx, goroutineDumpEndpoint)
115+
if err != nil {
116+
return err
117+
}
118+
119+
return copyToWriter(response, writer)
120+
}
121+
122+
func (client *httpClient) GetTunnelState(ctx context.Context) (*TunnelState, error) {
123+
response, err := client.GET(ctx, tunnelStateEndpoint)
124+
if err != nil {
125+
return nil, err
126+
}
127+
128+
defer response.Body.Close()
129+
130+
var state TunnelState
131+
if err := json.NewDecoder(response.Body).Decode(&state); err != nil {
132+
return nil, fmt.Errorf("failed to decode body: %w", err)
133+
}
134+
135+
return &state, nil
136+
}
137+
138+
func (client *httpClient) GetSystemInformation(ctx context.Context, writer io.Writer) error {
139+
response, err := client.GET(ctx, systemInformationEndpoint)
140+
if err != nil {
141+
return err
142+
}
143+
144+
return copyToWriter(response, writer)
145+
}
146+
147+
func (client *httpClient) GetMetrics(ctx context.Context, writer io.Writer) error {
148+
response, err := client.GET(ctx, metricsEndpoint)
149+
if err != nil {
150+
return err
151+
}
152+
153+
return copyToWriter(response, writer)
154+
}
155+
156+
func copyToWriter(response *http.Response, writer io.Writer) error {
157+
defer response.Body.Close()
158+
159+
_, err := io.Copy(writer, response.Body)
160+
if err != nil {
161+
return fmt.Errorf("error writing metrics: %w", err)
162+
}
163+
164+
return nil
97165
}
98166

99167
type HTTPClient interface {
100-
GetLogConfiguration(ctx context.Context) (LogConfiguration, error)
168+
GetLogConfiguration(ctx context.Context) (*LogConfiguration, error)
169+
GetMemoryDump(ctx context.Context, writer io.Writer) error
170+
GetGoroutineDump(ctx context.Context, writer io.Writer) error
171+
GetTunnelState(ctx context.Context) (*TunnelState, error)
172+
GetSystemInformation(ctx context.Context, writer io.Writer) error
173+
GetMetrics(ctx context.Context, writer io.Writer) error
101174
}

diagnostic/consts.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,12 @@ const (
1313
logFilename = "cloudflared_logs.txt" // name of the output log file
1414
configurationKeyUID = "uid" // Key used to set and get the UID value from the configuration map
1515
tailMaxNumberOfLines = "10000" // maximum number of log lines from a virtual runtime (docker or kubernetes)
16+
17+
// Endpoints used by the diagnostic HTTP Client.
18+
configurationEndpoint = "diag/configuration"
19+
tunnelStateEndpoint = "diag/tunnel"
20+
systemInformationEndpoint = "diag/system"
21+
memoryDumpEndpoint = "debug/pprof/heap"
22+
goroutineDumpEndpoint = "debug/pprof/goroutine"
23+
metricsEndpoint = "metrics"
1624
)

diagnostic/error.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,6 @@ var (
1717
ErrKeyNotFound = errors.New("key not found")
1818
// Error used when there is no disk volume information available.
1919
ErrNoVolumeFound = errors.New("no disk volume information found")
20+
// Error user when the base url of the diagnostic client is not provided.
21+
ErrNoBaseUrl = errors.New("no base url")
2022
)

diagnostic/handlers.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ func NewDiagnosticHandler(
5555
}
5656
}
5757

58+
func (handler *Handler) InstallEndpoints(router *http.ServeMux) {
59+
router.HandleFunc(configurationEndpoint, handler.ConfigurationHandler)
60+
router.HandleFunc(tunnelStateEndpoint, handler.TunnelStateHandler)
61+
router.HandleFunc(systemInformationEndpoint, handler.SystemHandler)
62+
}
63+
5864
func (handler *Handler) SystemHandler(writer http.ResponseWriter, request *http.Request) {
5965
logger := handler.log.With().Str(collectorField, systemCollectorName).Logger()
6066
logger.Info().Msg("Collection started")
@@ -95,7 +101,7 @@ func (handler *Handler) SystemHandler(writer http.ResponseWriter, request *http.
95101
}
96102
}
97103

98-
type tunnelStateResponse struct {
104+
type TunnelState struct {
99105
TunnelID uuid.UUID `json:"tunnelID,omitempty"`
100106
ConnectorID uuid.UUID `json:"connectorID,omitempty"`
101107
Connections []tunnelstate.IndexedConnectionInfo `json:"connections,omitempty"`
@@ -107,7 +113,7 @@ func (handler *Handler) TunnelStateHandler(writer http.ResponseWriter, _ *http.R
107113

108114
defer log.Info().Msg("Collection finished")
109115

110-
body := tunnelStateResponse{
116+
body := TunnelState{
111117
handler.tunnelID,
112118
handler.connectorID,
113119
handler.tracker.GetActiveConnections(),

diagnostic/handlers_test.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -186,12 +186,7 @@ func TestTunnelStateHandler(t *testing.T) {
186186
handler.TunnelStateHandler(recorder, nil)
187187
decoder := json.NewDecoder(recorder.Body)
188188

189-
var response struct {
190-
TunnelID uuid.UUID `json:"tunnelID,omitempty"`
191-
ConnectorID uuid.UUID `json:"connectorID,omitempty"`
192-
Connections []tunnelstate.IndexedConnectionInfo `json:"connections,omitempty"`
193-
}
194-
189+
var response diagnostic.TunnelState
195190
err := decoder.Decode(&response)
196191
require.NoError(t, err)
197192
assert.Equal(t, http.StatusOK, recorder.Code)

metrics/metrics.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,7 @@ func newMetricsHandler(
9494
})
9595
}
9696

97-
router.HandleFunc("/diag/configuration", config.DiagnosticHandler.ConfigurationHandler)
98-
router.HandleFunc("/diag/tunnel", config.DiagnosticHandler.TunnelStateHandler)
99-
router.HandleFunc("/diag/system", config.DiagnosticHandler.SystemHandler)
97+
config.DiagnosticHandler.InstallEndpoints(router)
10098

10199
return router
102100
}

0 commit comments

Comments
 (0)