Skip to content

Commit 430acf6

Browse files
committed
feat: build sequence using call and returns
1 parent 2adaf43 commit 430acf6

File tree

10 files changed

+203
-151
lines changed

10 files changed

+203
-151
lines changed

assets/templates/partials/parse_form_partial.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
<input type="checkbox" name="includeReturns" value="yes" {{ if .Sequence.IncludeReturns }}checked{{ end -}}>
2323
Include VCL returns
2424
</label>
25+
<label class="form-row">
26+
<input type="checkbox" name="trackURLAndHost" value="yes" {{ if .Sequence.TrackURLAndHost }}checked{{ end -}}>
27+
Track URL and Host
28+
</label>
2529

2630
<label class="form-row">
2731
Actor distance

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ module github.com/aorith/varnishlog-parser
33
go 1.25.3
44

55
require (
6-
github.com/aorith/svg-sequence v0.0.12
6+
github.com/aorith/svg-sequence v0.0.14
77
github.com/aorith/svg-timeline v0.2.1
88
)

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
github.com/aorith/svg-sequence v0.0.11 h1:LKVmz0h0N310uZrdPWObHqgVAVj+pqX2eH2hqyIhHIQ=
2-
github.com/aorith/svg-sequence v0.0.11/go.mod h1:8PeIQFIE65/ZgSbPwUvv5lfuaiv80V9x2t4NNRhI2jQ=
3-
github.com/aorith/svg-sequence v0.0.12 h1:r6WaUvSC9eDsLFyfoUH+Bs+8+aPmSyOs3uYESjQJME8=
4-
github.com/aorith/svg-sequence v0.0.12/go.mod h1:8PeIQFIE65/ZgSbPwUvv5lfuaiv80V9x2t4NNRhI2jQ=
1+
github.com/aorith/svg-sequence v0.0.13 h1:bRLN27OSoK0wsXOvzkNmpAdMuZDBueXxU14sFNoGusU=
2+
github.com/aorith/svg-sequence v0.0.13/go.mod h1:8PeIQFIE65/ZgSbPwUvv5lfuaiv80V9x2t4NNRhI2jQ=
3+
github.com/aorith/svg-sequence v0.0.14 h1:DCoSHVg37o7W3eRqbhEeAsIq/jbzrpkvCS2XTPg0XfI=
4+
github.com/aorith/svg-sequence v0.0.14/go.mod h1:8PeIQFIE65/ZgSbPwUvv5lfuaiv80V9x2t4NNRhI2jQ=
55
github.com/aorith/svg-timeline v0.2.1 h1:BKog2X8drlpTeIXZXa89OnxVaFr4IUhVA4/9q3BUsHE=
66
github.com/aorith/svg-timeline v0.2.1/go.mod h1:haohFRiwc0+Po6714AgYlsoeolDqRTNkI+d62k51qjo=

pkg/render/helpers.go

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,8 @@ func ttlRecordHTML(r vsl.TTLRecord) string {
8686
// timestampRecordHTML returns a Timestamp record in HTML format using abbr for human readable dates
8787
func timestampRecordHTML(r vsl.TimestampRecord) string {
8888
absTime := float64(r.AbsoluteTime.UnixMicro()) / 1e6
89-
if r.AbsoluteTime.Equal(r.StartTime) {
90-
return fmt.Sprintf(
91-
`%s | Elapsed: %s | Total: %s | <abbr title="%s">%.6f</abbr>`,
92-
r.EventLabel, r.SinceLast.String(), r.SinceStart.String(), r.AbsoluteTime.String(), absTime,
93-
)
94-
}
95-
startTime := float64(r.StartTime.UnixMicro()) / 1e6
9689
return fmt.Sprintf(
97-
`%s | Elapsed: %s | Total: %s | Start: <abbr title="%s">%.6f</abbr> End: <abbr title="%s">%.6f</abbr>`,
98-
r.EventLabel, r.SinceLast.String(), r.SinceStart.String(), r.StartTime.String(), startTime, r.AbsoluteTime.String(), absTime,
90+
`%s | Elapsed: %s | Total: %s | <abbr title="%s">%.6f</abbr>`,
91+
r.EventLabel, r.SinceLast.String(), r.SinceStart.String(), r.AbsoluteTime.String(), absTime,
9992
)
10093
}

pkg/render/sequence.go

Lines changed: 141 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package render
55
import (
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

3638
type 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\nESI 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+
}

pkg/render/vcltree.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,6 @@ func renderTxTree(s *rowBuilder, ts vsl.TransactionSet, tx *vsl.Transaction, vis
5454
s.addRow(r.GetTag(), "", record.String(), "")
5555
case vsl.HitRecord:
5656
s.addRow(r.GetTag(), "", record.String(), "")
57-
case vsl.HitMissRecord:
58-
s.addRow(r.GetTag(), "", record.String(), "")
5957
case vsl.GzipRecord:
6058
s.addRow(r.GetTag(), "", record.String(), "")
6159
case vsl.BackendOpenRecord:

pkg/server/routes.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ func indexHandler(version string) func(http.ResponseWriter, *http.Request) {
1919
data.Sequence.StepHeight = 40
2020
data.Sequence.IncludeCalls = false
2121
data.Sequence.IncludeReturns = false
22+
data.Sequence.TrackURLAndHost = false
2223

2324
data.Timeline.Sessions = false
2425
data.Timeline.Precision = 1200
@@ -81,6 +82,7 @@ func parseHandler(version string) func(http.ResponseWriter, *http.Request) {
8182

8283
data.Sequence.IncludeCalls = r.Form.Get("includeCalls") == "yes"
8384
data.Sequence.IncludeReturns = r.Form.Get("includeReturns") == "yes"
85+
data.Sequence.TrackURLAndHost = r.Form.Get("trackURLAndHost") == "yes"
8486

8587
// Timeline settings
8688
data.Timeline.Sessions = r.Form.Get("sessions") == "yes"

0 commit comments

Comments
 (0)