-
Notifications
You must be signed in to change notification settings - Fork 3
/
client.go
146 lines (129 loc) · 4.34 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package dotnetdiag
import (
"fmt"
"net"
)
// Client implement Diagnostic IPC Protocol client.
// https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md
type Client struct {
addr string
dial Dialer
}
// Dialer establishes connection to the given address. Due to the potential for
// an optional continuation in the Diagnostics IPC Protocol, each successful
// connection between the runtime and a Diagnostic Port is only usable once.
//
// Note that the dialer is OS-specific, refer to documentation for details:
// https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md#transport
type Dialer func(addr string) (net.Conn, error)
// Option overrides default Client parameters.
type Option func(*Client)
// WithDialer overrides default dialer function with d.
func WithDialer(d Dialer) Option {
return func(c *Client) {
c.dial = d
}
}
// Session represents EventPipe stream of NetTrace data created with
// `CollectTracing` command.
//
// A session is expected to be closed with `StopTracing` call (or `Close`),
// as there is a "run down" at the end of a stream session that transmits
// additional metadata. If the stream is stopped prematurely due to a client
// or server error, the NetTrace stream will be incomplete and should
// be considered corrupted.
type Session struct {
c *Client
conn net.Conn
ID uint64
}
// CollectTracingConfig contains supported parameters for CollectTracing command.
type CollectTracingConfig struct {
// CircularBufferSizeMB specifies the size of the circular buffer used for
// buffering event data while streaming
CircularBufferSizeMB uint32
// Providers member lists providers to turn on for a streaming session.
// See ETW documentation for a more detailed explanation of Keywords, Filters, and Log Level:
// https://docs.microsoft.com/en-us/message-analyzer/system-etw-provider-event-keyword-level-settings
Providers []ProviderConfig
}
// NewClient creates a new Diagnostic IPC Protocol client for the transport
// specified - on Unix/Linux based platforms, a Unix Domain Socket will be used, and
// on Windows, a Named Pipe will be used:
// - /tmp/dotnet-diagnostic-{%d:PID}-{%llu:disambiguation key}-socket (Linux/MacOS)
// - \\.\pipe\dotnet-diagnostic-{%d:PID} (Windows)
//
// Refer to documentation for details:
// https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md#transport
func NewClient(addr string, options ...Option) *Client {
c := &Client{addr: addr}
for _, option := range options {
option(c)
}
if c.dial == nil {
c.dial = DefaultDialer()
}
return c
}
// CollectTracing creates a new EventPipe session stream of NetTrace data.
func (c *Client) CollectTracing(config CollectTracingConfig) (s *Session, err error) {
// Every session has its own IPC connection which cannot be reused for any
// other purposes; in order to close the connection another connection
// to be opened - see `StopTracing`.
conn, err := c.dial(c.addr)
if err != nil {
return nil, err
}
defer func() {
// The connection should not be disposed if a session has been created.
if err != nil {
_ = conn.Close()
}
}()
p := CollectTracingPayload{
CircularBufferSizeMB: config.CircularBufferSizeMB,
Format: FormatNetTrace,
Providers: config.Providers,
}
if err = writeMessage(conn, CommandSetEventPipe, EventPipeCollectTracing, p.Bytes()); err != nil {
return nil, err
}
var resp CollectTracingResponse
if err = readResponse(conn, &resp); err != nil {
return nil, err
}
s = &Session{
c: c,
conn: conn,
ID: resp.SessionID,
}
return s, nil
}
// StopTracing stops the given streaming session started with CollectTracing.
func (c *Client) StopTracing(sessionID uint64) error {
conn, err := c.dial(c.addr)
if err != nil {
return err
}
defer func() {
_ = conn.Close()
}()
p := StopTracingPayload{SessionID: sessionID}
if err := writeMessage(conn, CommandSetEventPipe, EventPipeStopTracing, p.Bytes()); err != nil {
return err
}
var resp StopTracingResponse
if err := readResponse(conn, &resp); err != nil {
return err
}
if resp.SessionID != sessionID {
return fmt.Errorf("%w: %x", ErrSessionIDMismatch, resp.SessionID)
}
return nil
}
func (s *Session) Read(b []byte) (int, error) {
return s.conn.Read(b)
}
func (s *Session) Close() error {
return s.c.StopTracing(s.ID)
}