-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathmain.go
293 lines (243 loc) · 10.2 KB
/
main.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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
package main
import (
"flag"
"fmt"
"log"
"os"
"encoding/json"
"io/ioutil"
"path/filepath"
"github.com/awalterschulze/gographviz"
fi "github.com/numbleroot/nemo/faultinjectors"
gr "github.com/numbleroot/nemo/graphing"
re "github.com/numbleroot/nemo/report"
)
// Interfaces.
// FaultInjector
type FaultInjector interface {
LoadOutput() error
GetFailureSpec() *fi.FailureSpec
GetMsgsFailedRuns() [][]*fi.Message
GetOutput() []*fi.Run
GetRunsIters() []uint
GetSuccessRunsIters() []uint
GetFailedRunsIters() []uint
}
// GraphDatabase
type GraphDatabase interface {
InitGraphDB(string, []*fi.Run) error
CloseDB() error
LoadRawProvenance() error
SimplifyProv([]uint) error
CreateHazardAnalysis(string) ([]*gographviz.Graph, error)
CreatePrototypes([]uint, []uint) ([]string, [][]string, []string, [][]string, error)
PullPrePostProv() ([]*gographviz.Graph, []*gographviz.Graph, []*gographviz.Graph, []*gographviz.Graph, error)
CreateNaiveDiffProv(bool, []uint, *gographviz.Graph) ([]*gographviz.Graph, []*gographviz.Graph, [][]*fi.Missing, error)
GenerateCorrections() ([]string, error)
GenerateExtensions() (bool, []string, error)
}
// Reporter
type Reporter interface {
Prepare(string, string, string) error
GenerateFigure(string, *gographviz.Graph) error
GenerateFigures([]uint, string, []*gographviz.Graph) error
}
// Structs.
// DebugRun
type DebugRun struct {
workDir string
allResultsDir string
thisResultsDir string
faultInj FaultInjector
graphDB GraphDatabase
reporter Reporter
}
func main() {
// Define which flags are supported.
faultInjOutFlag := flag.String("faultInjOut", "", "Specify file system path to output directory of fault injector.")
graphDBConnFlag := flag.String("graphDBConn", "bolt://127.0.0.1:7687", "Supply connection URI to dockerized graph database.")
flag.Parse()
// Extract and check for existence of required ones.
faultInjOut := *faultInjOutFlag
if faultInjOut == "" {
log.Fatal("Please provide a fault injection output directory to analyze.")
}
graphDBConn := *graphDBConnFlag
// Determine current working directory.
curDir, err := filepath.Abs(".")
if err != nil {
log.Fatalf("Failed obtaining absolute current directory: %v", err)
}
// Start building structs.
debugRun := &DebugRun{
workDir: curDir,
allResultsDir: filepath.Join(curDir, "results"),
thisResultsDir: filepath.Join(curDir, "results", filepath.Base(faultInjOut)),
faultInj: &fi.Molly{
Run: filepath.Base(faultInjOut),
OutputDir: faultInjOut,
},
graphDB: &gr.Neo4J{},
reporter: &re.Report{},
}
// Ensure the results directory for this debug run exists.
err = os.MkdirAll(debugRun.allResultsDir, 0755)
if err != nil {
log.Fatalf("Could not ensure resDir exists: %v", err)
}
// Extract, transform, and load fault injector output.
err = debugRun.faultInj.LoadOutput()
if err != nil {
log.Fatalf("Failed to load output from Molly: %v", err)
}
// Graph queries.
// Determine the IDs of all and all failed executions.
iters := debugRun.faultInj.GetRunsIters()
failedIters := debugRun.faultInj.GetFailedRunsIters()
// Connect to graph database docker container.
err = debugRun.graphDB.InitGraphDB(graphDBConn, debugRun.faultInj.GetOutput())
if err != nil {
log.Fatalf("Failed to initialize connection to graph database: %v", err)
}
defer debugRun.graphDB.CloseDB()
// Load initial (naive) version of provenance
// graphs for antecedent and consequent.
err = debugRun.graphDB.LoadRawProvenance()
if err != nil {
log.Fatalf("Failed to import provenance (naive) into graph database: %v", err)
}
// Clean-up loaded provenance data and
// re-import in reduced versions.
err = debugRun.graphDB.SimplifyProv(iters)
if err != nil {
log.Fatalf("Could not clean-up initial provenance data: %v", err)
}
// Create hazard analysis DOT figure.
hazardDots, err := debugRun.graphDB.CreateHazardAnalysis(faultInjOut)
if err != nil {
log.Fatalf("Failed to perform hazard analysis of simulation: %v", err)
}
// Extract prototypes of successful and
// failed runs (skeletons) and import.
interProto, interProtoMiss, unionProto, unionProtoMiss, err := debugRun.graphDB.CreatePrototypes(debugRun.faultInj.GetSuccessRunsIters(), debugRun.faultInj.GetFailedRunsIters())
if err != nil {
log.Fatalf("Failed to create prototypes of successful executions: %v", err)
}
// Pull antecedent and consequent provenance
// and create DOT diagram strings.
preProvDots, postProvDots, preCleanProvDots, postCleanProvDots, err := debugRun.graphDB.PullPrePostProv()
if err != nil {
log.Fatalf("Failed to pull and generate antecedent and consequent provenance DOT: %v", err)
}
// Create differential provenance graphs for
// consequent provenance.
naiveDiffDots, naiveFailedDots, missingEvents, err := debugRun.graphDB.CreateNaiveDiffProv(false, debugRun.faultInj.GetFailedRunsIters(), postProvDots[0])
if err != nil {
log.Fatalf("Could not create differential provenance between successful and failed provenance: %v", err)
}
var corrections []string
if len(failedIters) > 0 {
// Generate correction suggestions for moving towards correctness.
corrections, err = debugRun.graphDB.GenerateCorrections()
if err != nil {
log.Fatalf("Error while generating corrections: %v", err)
}
}
// Attempt to create extension proposals in case
// the antecedent depends on network events.
allRunsAchievedPre, extensions, err := debugRun.graphDB.GenerateExtensions()
if err != nil {
log.Fatalf("Error while generating extensions: %v", err)
}
// Reporting.
// Retrieve current state of run output.
// Enrich with missing events.
runs := debugRun.faultInj.GetOutput()
for i := range iters {
// Progressively formulate one top-level recommendation
// for programmers to focus on first.
if len(corrections) > 0 {
// We observed an specification violation. Suggest corrections first.
runs[iters[i]].Recommendation = append(runs[iters[i]].Recommendation, "A fault occurred. Let's try making the protocol correct first.")
runs[iters[i]].Recommendation = append(runs[iters[i]].Recommendation, corrections...)
} else if len(extensions) > 0 {
// In case there exist runs in this execution where the
// antecedent was not achieved (not per se a problem!)
// and communication had to be performed for the successful
// run to establish the antecedent, it might be a good
// idea for the system designers to make sure these rules
// are maximum fault-tolerant.
runs[iters[i]].Recommendation = append(runs[iters[i]].Recommendation, "Good job, no specification violation. At least one run did not establish the antecedent, though. Maybe double-check the fault tolerance of the following rules:")
runs[iters[i]].Recommendation = append(runs[iters[i]].Recommendation, extensions...)
} else if !allRunsAchievedPre {
// We saw a bug, but we don't find corrections or extensions
// to suggest. This must be a bug outside our capabilities
// (e.g., local-logic).
runs[iters[i]].Recommendation = append(runs[iters[i]].Recommendation, "Nemo can't help with this type of bug. Please use the graphs below regarding differential provenance for guidance to root cause.")
} else {
// No specification violation happened, no more fault tolerance to add.
runs[iters[i]].Recommendation = append(runs[iters[i]].Recommendation, "Well done! No faults, no missing fault tolerance.")
}
runs[iters[i]].InterProto = interProto
runs[iters[i]].UnionProto = unionProto
}
j := 0
for i := range failedIters {
runs[failedIters[i]].Corrections = corrections
runs[failedIters[i]].MissingEvents = missingEvents[j]
runs[failedIters[i]].InterProtoMissing = interProtoMiss[j]
runs[failedIters[i]].UnionProtoMissing = unionProtoMiss[j]
j++
}
// Marshal collected debugging information to JSON.
debuggingJSON, err := json.Marshal(runs)
if err != nil {
log.Fatalf("Failed to marshal debugging information to JSON: %v", err)
}
// Prepare report webpage containing all insights and suggestions.
err = debugRun.reporter.Prepare(debugRun.workDir, debugRun.allResultsDir, debugRun.thisResultsDir)
if err != nil {
log.Fatalf("Failed to prepare debugging report: %v", err)
}
// Write debugging JSON to file 'debugging.json'.
err = ioutil.WriteFile(filepath.Join(debugRun.thisResultsDir, "debugging.json"), debuggingJSON, 0644)
if err != nil {
log.Fatalf("Error writing out debugging.json: %v", err)
}
// Generate and write-out hazard analysis figures.
err = debugRun.reporter.GenerateFigures(iters, "spacetime", hazardDots)
if err != nil {
log.Fatalf("Could not generate hazard analysis figures for report: %v", err)
}
// Generate and write-out antecedent provenance figures.
err = debugRun.reporter.GenerateFigures(iters, "pre_prov", preProvDots)
if err != nil {
log.Fatalf("Could not generate antecedent provenance figures for report: %v", err)
}
// Generate and write-out consequent provenance figures.
err = debugRun.reporter.GenerateFigures(iters, "post_prov", postProvDots)
if err != nil {
log.Fatalf("Could not generate consequent provenance figures for report: %v", err)
}
// Generate and write-out cleaned-up antecedent provenance figures.
err = debugRun.reporter.GenerateFigures(iters, "pre_prov_clean", preCleanProvDots)
if err != nil {
log.Fatalf("Could not generate cleaned-up antecedent provenance figures for report: %v", err)
}
// Generate and write-out cleaned-up consequent provenance figures.
err = debugRun.reporter.GenerateFigures(iters, "post_prov_clean", postCleanProvDots)
if err != nil {
log.Fatalf("Could not generate cleaned-up consequent provenance figures for report: %v", err)
}
// Generate and write-out naive differential provenance (diff) figures.
err = debugRun.reporter.GenerateFigures(failedIters, "diff_post_prov-diff", naiveDiffDots)
if err != nil {
log.Fatalf("Could not generate naive differential provenance (diff) figures for report: %v", err)
}
// Generate and write-out naive differential provenance (failed) figures.
err = debugRun.reporter.GenerateFigures(failedIters, "diff_post_prov-failed", naiveFailedDots)
if err != nil {
log.Fatalf("Could not generate naive differential provenance (failed) figures for report: %v", err)
}
fmt.Printf("All done! Find the debug report here: %s\n\n", filepath.Join(debugRun.thisResultsDir, "index.html"))
}