@@ -5,6 +5,7 @@ package render
55import (
66 "fmt"
77 "log/slog"
8+ "slices"
89 "strings"
910 "unicode/utf8"
1011
@@ -31,13 +32,15 @@ const (
3132 ColorReturn = "#995599"
3233 ColorHit = "#115F00"
3334 ColorGray = "#707070"
35+ ColorTrack = "#492020"
3436)
3537
3638type SequenceConfig struct {
37- Distance int // distance between actors
38- StepHeight int // height between each step
39- IncludeCalls bool // whether to include all VCL calls
40- IncludeReturns bool // whether to include all VCL returns
39+ Distance int // distance between actors
40+ StepHeight int // height between each step
41+ IncludeCalls bool // whether to include all VCL calls
42+ IncludeReturns bool // whether to include all VCL returns
43+ TrackURLAndHost bool // whether to track all modifications to the URL and Host
4144}
4245
4346// Sequence returns a sequence diagram rendered as an SVG image.
@@ -54,10 +57,23 @@ func Sequence(ts vsl.TransactionSet, root *vsl.Transaction, cfg SequenceConfig)
5457 if cfg .StepHeight != 0 {
5558 s .SetStepHeight (cfg .StepHeight )
5659 }
57- s .AddActors (C , V , H , B )
5860
5961 visited := make (map [vsl.TXID ]bool )
6062 addTransactionLogs (s , ts , root , cfg , visited )
63+
64+ // Ensure correct actor ordering
65+ finalActors := []string {}
66+ for _ , a := range s .Actors () {
67+ if a != C && a != V && a != H && a != B {
68+ finalActors = append (finalActors , a )
69+ }
70+ }
71+ if slices .Contains (s .Actors (), C ) {
72+ finalActors = append (finalActors , C )
73+ }
74+ finalActors = append (finalActors , V , H , B )
75+ s .AddActors (finalActors ... )
76+
6177 s .CloseAllSections ()
6278
6379 svg , err := s .Generate ()
@@ -90,113 +106,110 @@ func addTransactionLogs(s *svgsequence.Sequence, ts vsl.TransactionSet, tx *vsl.
90106 reqProcessed = & HTTPRequest {}
91107 }
92108
93- // When processing ESI, if we add the response status step at the exact moment
94- // that the RespStatus record is logged, it appears before ESI processing and looks weird
95- // so save it here and draw it on request end
96- var respStep * svgsequence.Step
109+ client := C
110+ reqStart := tx .NextRecordByTag (tags .ReqStart , 0 )
111+ if reqStart != nil {
112+ reqStartRecord := reqStart .(vsl.ReqStartRecord )
113+ client = truncateStr (reqStartRecord .ClientIP .String (), 20 )
114+ }
115+
116+ truncateLen := 32
117+ if cfg .Distance != 0 {
118+ truncateLen = cfg .Distance / 6 - 1
119+ }
97120
98- for _ , r := range tx .Records {
121+ for i , r := range tx .Records {
99122 switch record := r .(type ) {
100123 case vsl.BeginRecord :
101124 s .OpenSection (string (tx .TXID ), getTxTypeColor (tx .TXType ))
102125
103- case vsl.ReqStartRecord :
104- if tx .ESILevel > 0 {
105- start := fmt .Sprintf ("%s\n ESI Level %d" , requestSequence (reqReceived ), tx .ESILevel )
106- s .AddStep (svgsequence.Step {SourceActor : V , TargetActor : V , Description : start })
107- s .AddStep (svgsequence.Step {SourceActor : V , TargetActor : H , Description : requestSequence (reqProcessed )})
108- } else {
109- start := fmt .Sprintf ("%s\n %s %s" , requestSequence (reqReceived ), record .ClientIP , record .Listener )
110- s .AddStep (svgsequence.Step {SourceActor : C , TargetActor : V , Description : start })
111- s .AddStep (svgsequence.Step {SourceActor : V , TargetActor : H , Description : requestSequence (reqProcessed )})
112- }
113-
114126 case vsl.EndRecord :
115- if respStep != nil {
116- s .AddStep (* respStep )
117- }
118127 s .CloseSection ()
119128
120- case vsl.SessOpenRecord :
121- s .AddStep (svgsequence.Step {
122- SourceActor : C ,
123- TargetActor : V ,
124- Description : record .GetTag () + " " + record .RemoteAddr .String (),
125- })
126-
127- case vsl.SessCloseRecord :
128- s .AddStep (svgsequence.Step {
129- SourceActor : V ,
130- TargetActor : C ,
131- Description : record .String (),
132- })
133-
134- case vsl.VCLLogRecord :
135- s .AddStep (svgsequence.Step {
136- SourceActor : V ,
137- TargetActor : V ,
138- Description : record .String (),
139- Color : ColorGray ,
140- })
141-
142129 case vsl.VCLCallRecord :
143130 if cfg .IncludeCalls {
144131 s .AddStep (svgsequence.Step {SourceActor : V , TargetActor : V , Description : "call " + record .GetRawValue (), Color : ColorCall })
145132 }
146133
147134 switch r .GetRawValue () {
148- case "HIT" , "MISS" , "PASS" :
135+ case "RECV" :
136+ s .AddStep (svgsequence.Step {SourceActor : client , TargetActor : V , Description : drawRequest (reqReceived , truncateLen )})
137+
138+ case "HASH" :
139+ s .AddStep (svgsequence.Step {SourceActor : V , TargetActor : H , Description : "HASH" })
140+
141+ case "HIT" :
142+ hitRecord := getLastHitRecord (tx , i )
143+ s1 := ""
144+ if hitRecord == nil {
145+ s1 = "HIT"
146+ } else {
147+ if hitRecord .Fetched > 0 {
148+ s1 += "Streaming-"
149+ }
150+ s1 += fmt .Sprintf ("%s\n " , hitRecord .Tag )
151+ s1 += wrapAndTruncate (hitRecord .String (), truncateLen , 100 )
152+ }
153+ s .AddStep (svgsequence.Step {SourceActor : H , TargetActor : V , Description : s1 , Color : ColorHit })
154+
155+ case "MISS" , "PASS" :
149156 s .AddStep (svgsequence.Step {SourceActor : H , TargetActor : V , Description : r .GetRawValue ()})
150157
151158 case "SYNTH" :
152- if respStep != nil {
153- s . AddStep (svgsequence. Step { SourceActor : V , TargetActor : V , Description : "SYNTH \n " + respStep . Description } )
154- respStep = nil
155- } else {
156- s . AddStep (svgsequence. Step { SourceActor : V , TargetActor : V , Description : r .GetRawValue ()} )
159+ lastStatus := tx . LastRecordByTag ( tags . RespStatus , i )
160+ lastReason := tx . LastRecordByTag ( tags . RespReason , i )
161+ s1 := "SYNTH"
162+ if lastStatus != nil {
163+ s1 += " \n " + lastStatus .GetRawValue ()
157164 }
165+ if lastReason != nil {
166+ s1 += " " + wrapAndTruncate (lastReason .GetRawValue (), truncateLen , 100 )
167+ }
168+ s .AddStep (svgsequence.Step {SourceActor : V , TargetActor : V , Description : s1 })
158169
159170 case "PIPE" :
160- s .AddStep (svgsequence.Step {SourceActor : V , TargetActor : C , Description : r .GetRawValue ()})
171+ s .AddStep (svgsequence.Step {SourceActor : V , TargetActor : V , Description : r .GetRawValue ()})
161172
162173 case "BACKEND_FETCH" :
163- s .AddStep (svgsequence.Step {SourceActor : V , TargetActor : B , Description : requestSequence (reqProcessed )})
174+ s .AddStep (svgsequence.Step {SourceActor : V , TargetActor : B , Description : drawRequest (reqProcessed , truncateLen )})
164175
165- }
176+ case "BACKEND_RESPONSE" :
177+ // handled at return deliver
166178
167- case vsl.HitRecord :
168- s1 := ""
169- if record .Fetched > 0 {
170- s1 += "Streaming-"
171179 }
172- s1 += record .GetTag () + "\n " + wrapAndTruncate (record .String (), 33 , 120 )
173- s .AddStep (svgsequence.Step {SourceActor : H , TargetActor : H , Description : s1 , Color : ColorHit })
174-
175- case vsl.HitMissRecord :
176- s1 := fmt .Sprintf ("%s TTL: %s" , record .GetTag (), record .TTL .String ())
177- s .AddStep (svgsequence.Step {SourceActor : H , TargetActor : H , Description : s1 , Color : ColorHit })
178180
179181 case vsl.VCLReturnRecord :
180182 if cfg .IncludeReturns {
181183 s .AddStep (svgsequence.Step {SourceActor : V , TargetActor : V , Description : "return " + record .GetRawValue (), Color : ColorReturn })
182184 }
183185
184- case vsl.StatusRecord :
185- switch record .GetTag () {
186- case tags .BerespStatus :
187- s1 := statusSequence (tx , record .Status , tags .BerespReason )
188- respStep = & svgsequence.Step {SourceActor : B , TargetActor : V , Description : s1 }
189-
190- case tags .RespStatus :
191- s1 := statusSequence (tx , record .Status , tags .RespReason )
192- if respStep != nil {
193- // Probably a synth response
194- s .AddStep (svgsequence.Step {SourceActor : V , TargetActor : V , Description : respStep .Description })
195- }
196- if tx .ESILevel == 0 {
197- respStep = & svgsequence.Step {SourceActor : V , TargetActor : C , Description : s1 }
198- } else {
199- respStep = & svgsequence.Step {SourceActor : V , TargetActor : V , Description : s1 }
186+ switch r .GetRawValue () {
187+ case "deliver" :
188+ switch tx .TXType {
189+ case vsl .TxTypeRequest :
190+ lastStatus := tx .LastRecordByTag (tags .RespStatus , i )
191+ // If a RespStatus is not found, we are probably serving a cache hit
192+ if lastStatus != nil {
193+ s1 := "DELIVER\n " + lastStatus .GetRawValue ()
194+ lastReason := tx .LastRecordByTag (tags .RespReason , i )
195+ if lastReason != nil {
196+ s1 += " " + wrapAndTruncate (lastReason .GetRawValue (), truncateLen , 100 )
197+ }
198+ s .AddStep (svgsequence.Step {SourceActor : V , TargetActor : client , Description : s1 })
199+ }
200+
201+ case vsl .TxTypeBereq :
202+ lastStatus := tx .LastRecordByTag (tags .BerespStatus , i )
203+ s1 := "BACKEND_RESPONSE"
204+ if lastStatus != nil {
205+ s1 += "\n " + lastStatus .GetRawValue ()
206+ lastReason := tx .LastRecordByTag (tags .BerespReason , i )
207+ if lastReason != nil {
208+ s1 += " " + wrapAndTruncate (lastReason .GetRawValue (), truncateLen , 100 )
209+ }
210+ }
211+ s .AddStep (svgsequence.Step {SourceActor : B , TargetActor : V , Description : s1 })
212+
200213 }
201214 }
202215
@@ -205,15 +218,41 @@ func addTransactionLogs(s *svgsequence.Sequence, ts vsl.TransactionSet, tx *vsl.
205218 SourceActor : B , TargetActor : B ,
206219 Description : fmt .Sprintf (
207220 "Backend: %s\n %s %s:%d" ,
208- truncateStrMiddle (record .Name , 40 ),
221+ truncateStrMiddle (record .Name , truncateLen ),
209222 record .Reason ,
210223 record .RemoteAddr .String (),
211224 record .RemotePort ,
212225 ),
213226 })
214227
215228 case vsl.FetchErrorRecord :
216- s .AddStep (svgsequence.Step {SourceActor : B , TargetActor : B , Description : wrapAndTruncate (record .GetRawValue (), 35 , 100 ), Color : ColorError })
229+ s .AddStep (svgsequence.Step {SourceActor : B , TargetActor : B , Description : wrapAndTruncate (record .GetRawValue (), truncateLen , 100 ), Color : ColorError })
230+
231+ case vsl.URLRecord :
232+ if cfg .TrackURLAndHost {
233+ s .AddStep (svgsequence.Step {
234+ SourceActor : V ,
235+ TargetActor : V ,
236+ Description : truncateStr ("URL: " + record .Path ()+ record .QueryString (), truncateLen ),
237+ Color : ColorTrack ,
238+ })
239+ }
240+
241+ case vsl.HeaderRecord :
242+ if cfg .TrackURLAndHost {
243+ // Header name should be already in canonical format
244+ if record .Name == "Host" {
245+ s .AddStep (svgsequence.Step {SourceActor : V , TargetActor : V , Description : truncateStr (record .Name + ": " + record .Value , truncateLen ), Color : ColorTrack })
246+ }
247+ }
248+
249+ case vsl.VCLLogRecord :
250+ s .AddStep (svgsequence.Step {
251+ SourceActor : V ,
252+ TargetActor : V ,
253+ Description : record .String (),
254+ Color : ColorGray ,
255+ })
217256
218257 case vsl.LinkRecord :
219258 childTx := ts .GetTX (record .VXID )
@@ -235,18 +274,11 @@ func addTransactionLogs(s *svgsequence.Sequence, ts vsl.TransactionSet, tx *vsl.
235274 }
236275}
237276
238- func requestSequence (req * HTTPRequest ) string {
277+ func drawRequest (req * HTTPRequest , truncateLen int ) string {
239278 method := req .method
240279 url := req .url
241280 host := req .host
242- return method + " " + truncateStr (url , 40 ) + "\n " + host
243- }
244-
245- func statusSequence (tx * vsl.Transaction , status int , reasonTag string ) string {
246- s := fmt .Sprintf ("%d" , status )
247- reason := tx .RecordValueByTag (reasonTag , false )
248- s += " " + wrapAndTruncate (reason , 35 , 100 )
249- return s
281+ return method + " " + truncateStr (url , truncateLen ) + "\n " + host
250282}
251283
252284// truncateStr trims the input string to a maximum length, appending "…" if it exceeds the length.
@@ -334,3 +366,20 @@ func getTxTypeColor(txType vsl.TxType) string {
334366 }
335367 return ColorReq
336368}
369+
370+ func getLastHitRecord (tx * vsl.Transaction , index int ) * vsl.HitRecord {
371+ var r vsl.Record
372+ r = tx .LastRecordByTag (tags .Hit , index )
373+ if r == nil {
374+ r = tx .LastRecordByTag (tags .HitMiss , index )
375+ }
376+ if r == nil {
377+ r = tx .LastRecordByTag (tags .HitPass , index )
378+ }
379+
380+ record , ok := r .(vsl.HitRecord )
381+ if ok {
382+ return & record
383+ }
384+ return nil
385+ }
0 commit comments