@@ -5,23 +5,14 @@ import (
55 "fmt"
66 "html/template"
77 "io"
8- "net/http"
98 "runtime"
10- "sync"
11- "sync/atomic"
129 "time"
1310
1411 "github.com/HdrHistogram/hdrhistogram-go"
1512 "github.com/symonk/vessel/internal/config"
16- "github.com/symonk/vessel/internal/trace "
13+ "github.com/symonk/vessel/internal/stats "
1714)
1815
19- // Recorder is the interface for something which records metrics from
20- // a HTTP Request->Response interactions.
21- type Recorder interface {
22- Record (response * http.Response , latency time.Duration , receivedBytes int64 , sentBytes int64 , err error )
23- }
24-
2516// Summariser is the interface for something which can display summary
2617// information.
2718type Summariser interface {
@@ -30,7 +21,6 @@ type Summariser interface {
3021
3122type ResultCollector interface {
3223 Summariser
33- Recorder
3424}
3525
3626// EventCollector collects execution data during the lifecycle of
@@ -55,76 +45,67 @@ type EventCollector struct {
5545 collectionRegistered time.Time
5646 rawErrors error
5747 errGrouper * ErrorGrouper
58- mu sync.Mutex
5948 latency hdrhistogram.Histogram
60- bytesReceived atomic. Int64
61- bytesSent atomic. Int64
49+ bytesReceived int64
50+ bytesSent int64
6251 waitingDns time.Duration
6352 waitingTls time.Duration
6453 waitingConnect time.Duration
65- newConnections atomic. Int64
54+ newConnections int64
6655 waitingGetConn time.Duration
56+ ingress chan * stats.Stats
6757}
6858
69- func New (writer io.Writer , cfg * config.Config ) * EventCollector {
70- return & EventCollector {
59+ func New (ingress chan * stats. Stats , writer io.Writer , cfg * config.Config ) * EventCollector {
60+ e := & EventCollector {
7161 counter : NewStatusCodeCounter (),
7262 cfg : cfg ,
7363 writer : writer ,
7464 collectionRegistered : time .Now (),
7565 latency : * hdrhistogram .New (1 , 60000 , 3 ),
7666 rawErrors : nil ,
7767 errGrouper : NewErrGrouper (),
68+ ingress : ingress ,
7869 }
70+ go e .listen ()
71+ return e
7972}
8073
81- // Record captures information about the completed request.
82- // It keeps mutex locking to a minimum where possible and favours
83- // CPU atomic operations where possible.
84- func (e * EventCollector ) Record (response * http.Response , latency time.Duration , bytesReceived int64 , bytesSent int64 , err error ) {
85- // It is possible response is nil in error cases.
86- // Keep a reference to the error, we will categorise them later
87- // based on the different types.
88- if err != nil {
89- e .mu .Lock ()
90- defer e .mu .Unlock ()
91- e .rawErrors = errors .Join (e .rawErrors , err )
92- e .errGrouper .Record (err )
93- // TODO: Error grouping for smarter summarising.
94- // TODO: Implement a way to 'classify' the errors into appropriate groups.
95- return
96- }
97-
98- // Pull out 'trace' data from the requests to paint a better picture in the summary
99- // of how time was spent from a granular point of view.
100- v := response .Request .Context ().Value (trace .TraceDataKey )
101- t , ok := v .(* trace.Trace )
102- if ok {
103- e .mu .Lock ()
104- e .waitingDns += t .DnsDone
105- e .waitingTls += t .TlsDone
106- e .waitingConnect += t .ConnectDone
107- e .waitingGetConn += t .GotConnection
108- e .mu .Unlock ()
74+ // listen waits for stats from the worker pool before incremental internal
75+ // values in preparation for summary generation later.
76+ //
77+ // For now this is a single listener, but eventually the channel can be fanned
78+ // out for reads and merged back into a single result chan for efficiency.
79+ func (e * EventCollector ) listen () {
80+ fmt .Println ("listening for stats" )
81+ for stat := range e .ingress {
82+ err := stat .Err
83+ if err != nil {
84+ e .rawErrors = errors .Join (e .rawErrors , err )
85+ e .errGrouper .Record (err )
86+ }
87+ e .waitingDns += stat .TimeOnDns
88+ e .waitingTls += stat .TimeOnTls
89+ e .waitingConnect += stat .TimeOnConnect
90+ e .waitingGetConn += stat .TimeOnConn
91+
92+ // We have a semi-successful response (in that sense that no error was returned)
93+ // Capture the histogram data for the latency of the response.
94+ e .latency .RecordValue (stat .Latency .Milliseconds ())
95+ e .counter .Increment (stat .StatusCode )
96+
97+ // Track the byte size of the initial request aswell as content type of
98+ // the response from the server. The collector is not responsible for
99+ // reading the response, this should be handled elsewhere to ensure safety
100+ // of reading responses and avoiding attempting multiple reads etc.
101+ e .bytesReceived += stat .BytesReceived
102+ e .bytesSent += stat .BytesSent
103+
104+ // Keep track of keep-alives etc, useful for detecting if there is an issue
105+ // with your server, or our client.
106+ e .newConnections += stat .ReusedConn
109107 }
110108
111- // We have a semi-successful response (in that sense that no error was returned)
112- // Capture the histogram data for the latency of the response.
113- e .latency .RecordValue (latency .Milliseconds ())
114- e .counter .Increment (response .StatusCode )
115-
116- // Track the byte size of the initial request aswell as content type of
117- // the response from the server. The collector is not responsible for
118- // reading the response, this should be handled elsewhere to ensure safety
119- // of reading responses and avoiding attempting multiple reads etc.
120- e .bytesReceived .Add (bytesReceived )
121- e .bytesSent .Add (bytesSent )
122-
123- // Keep track of keep-alives etc, useful for detecting if there is an issue
124- // with your server, or our client.
125- if ! t .ReusedConnection {
126- e .newConnections .Add (1 )
127- }
128109}
129110
130111// Summarise calculates the final summary prior to exiting.
@@ -179,11 +160,11 @@ Waiting: {{.Waiting}}
179160`
180161 // TODO: Smarter use of different terms, if the test was < 1MB transffered for example
181162 // fallback to bytesReceived/sec etc etc.
182- bytesReceived := e .bytesReceived . Load ()
163+ bytesReceived := e .bytesReceived
183164 receivedBytesPerSecond := (bytesReceived / int64 (seconds ))
184165 receivedMegabytes := float64 (receivedBytesPerSecond ) / 1_000_000
185166
186- bytesSent := e .bytesSent . Load ()
167+ bytesSent := e .bytesSent
187168 sentBytesPerSecond := (bytesSent / int64 (seconds ))
188169 sentMegabytes := float64 (sentBytesPerSecond ) / 1_000_000
189170
@@ -225,7 +206,7 @@ Waiting: {{.Waiting}}
225206 Workers : e .cfg .Concurrency ,
226207 Version : e .cfg .Version ,
227208 Waiting : waiting ,
228- OpenedConnections : e .newConnections . Load () ,
209+ OpenedConnections : e .newConnections ,
229210 MaxProcs : runtime .GOMAXPROCS (0 ),
230211 BytesTotal : fmt .Sprintf ("%.2FMB" , bytesTotal ),
231212 }
0 commit comments