-
Notifications
You must be signed in to change notification settings - Fork 52
/
visualization.go
131 lines (121 loc) Β· 3.88 KB
/
visualization.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
package porcupine
import (
"embed"
"encoding/json"
"fmt"
"io"
"os"
"sort"
)
type historyElement struct {
ClientId int
Start int64
End int64
Description string
}
type linearizationStep struct {
Index int
StateDescription string
}
type partialLinearization = []linearizationStep
type partitionVisualizationData struct {
History []historyElement
PartialLinearizations []partialLinearization
Largest map[int]int
}
type visualizationData = []partitionVisualizationData
func computeVisualizationData(model Model, info LinearizationInfo) visualizationData {
model = fillDefault(model)
data := make(visualizationData, len(info.history))
for partition := 0; partition < len(info.history); partition++ {
// history
n := len(info.history[partition]) / 2
history := make([]historyElement, n)
callValue := make(map[int]interface{})
returnValue := make(map[int]interface{})
for _, elem := range info.history[partition] {
switch elem.kind {
case callEntry:
history[elem.id].ClientId = elem.clientId
history[elem.id].Start = elem.time
callValue[elem.id] = elem.value
case returnEntry:
history[elem.id].End = elem.time
history[elem.id].Description = model.DescribeOperation(callValue[elem.id], elem.value)
returnValue[elem.id] = elem.value
}
}
// partial linearizations
largestIndex := make(map[int]int)
largestSize := make(map[int]int)
linearizations := make([]partialLinearization, len(info.partialLinearizations[partition]))
partials := info.partialLinearizations[partition]
sort.Slice(partials, func(i, j int) bool {
return len(partials[i]) > len(partials[j])
})
for i, partial := range partials {
linearization := make(partialLinearization, len(partial))
state := model.Init()
for j, histId := range partial {
var ok bool
ok, state = model.Step(state, callValue[histId], returnValue[histId])
if !ok {
panic("valid partial linearization returned non-ok result from model step")
}
stateDesc := model.DescribeState(state)
linearization[j] = linearizationStep{histId, stateDesc}
if largestSize[histId] < len(partial) {
largestSize[histId] = len(partial)
largestIndex[histId] = i
}
}
linearizations[i] = linearization
}
data[partition] = partitionVisualizationData{
History: history,
PartialLinearizations: linearizations,
Largest: largestIndex,
}
}
return data
}
// Visualize produces a visualization of a history and (partial) linearization
// as an HTML file that can be viewed in a web browser.
//
// If the history is linearizable, the visualization shows the linearization of
// the history. If the history is not linearizable, the visualization shows
// partial linearizations and illegal linearization points.
//
// To get the LinearizationInfo that this function requires, you can use
// [CheckOperationsVerbose] / [CheckEventsVerbose].
//
// This function writes the visualization, an HTML file with embedded
// JavaScript and data, to the given output.
func Visualize(model Model, info LinearizationInfo, output io.Writer) error {
data := computeVisualizationData(model, info)
jsonData, err := json.Marshal(data)
if err != nil {
return err
}
templateB, _ := visualizationFS.ReadFile("visualization/index.html")
template := string(templateB)
css, _ := visualizationFS.ReadFile("visualization/index.css")
js, _ := visualizationFS.ReadFile("visualization/index.js")
_, err = fmt.Fprintf(output, template, css, js, jsonData)
if err != nil {
return err
}
return nil
}
// VisualizePath is a wrapper around [Visualize] to write the visualization to
// a file path.
func VisualizePath(model Model, info LinearizationInfo, path string) error {
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
return Visualize(model, info, f)
}
//go:embed visualization
var visualizationFS embed.FS