github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/syz-verifier/stats.go (about) 1 // Copyright 2021 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 // TODO: switch syz-verifier to use syz-fuzzer. 5 6 //go:build ignore 7 8 package main 9 10 import ( 11 "fmt" 12 "sort" 13 "strings" 14 "sync" 15 "time" 16 17 "github.com/google/syzkaller/prog" 18 ) 19 20 type StatUint64 struct { 21 uint64 22 mu *sync.Mutex 23 } 24 25 func (s *StatUint64) Inc() { 26 s.mu.Lock() 27 defer s.mu.Unlock() 28 s.uint64++ 29 } 30 31 func (s *StatUint64) Get() uint64 { 32 s.mu.Lock() 33 defer s.mu.Unlock() 34 return s.uint64 35 } 36 37 type StatTime struct { 38 time.Time 39 mu *sync.Mutex 40 } 41 42 func (s *StatTime) Set(t time.Time) { 43 s.mu.Lock() 44 defer s.mu.Unlock() 45 s.Time = t 46 } 47 48 func (s *StatTime) Get() time.Time { 49 s.mu.Lock() 50 defer s.mu.Unlock() 51 return s.Time 52 } 53 54 type mapStringToCallStats map[string]*CallStats 55 56 type StatMapStringToCallStats struct { 57 mapStringToCallStats 58 mu *sync.Mutex 59 } 60 61 func (stat *StatMapStringToCallStats) IncCallOccurrenceCount(key string) { 62 stat.mu.Lock() 63 stat.mapStringToCallStats[key].Occurrences++ 64 stat.mu.Unlock() 65 } 66 67 func (stat *StatMapStringToCallStats) AddState(key string, state ReturnState) { 68 stat.mu.Lock() 69 stat.mapStringToCallStats[key].States[state] = true 70 stat.mu.Unlock() 71 } 72 73 func (stat *StatMapStringToCallStats) SetCallInfo(key string, info *CallStats) { 74 stat.mu.Lock() 75 stat.mapStringToCallStats[key] = info 76 stat.mu.Unlock() 77 } 78 79 func (stat *StatMapStringToCallStats) totalExecuted() uint64 { 80 stat.mu.Lock() 81 defer stat.mu.Unlock() 82 83 var t uint64 84 for _, cs := range stat.mapStringToCallStats { 85 t += cs.Occurrences 86 } 87 return t 88 } 89 90 func (stat *StatMapStringToCallStats) orderedStats() []*CallStats { 91 stat.mu.Lock() 92 defer stat.mu.Unlock() 93 94 css := make([]*CallStats, 0) 95 for _, cs := range stat.mapStringToCallStats { 96 if cs.Mismatches > 0 { 97 css = append(css, cs) 98 } 99 } 100 101 sort.Slice(css, func(i, j int) bool { 102 return getPercentage(css[i].Mismatches, css[i].Occurrences) > getPercentage(css[j].Mismatches, css[j].Occurrences) 103 }) 104 105 return css 106 } 107 108 // Stats encapsulates data for creating statistics related to the results 109 // of the verification process. 110 type Stats struct { 111 mu sync.Mutex 112 TotalCallMismatches StatUint64 113 TotalProgs StatUint64 114 ExecErrorProgs StatUint64 115 FlakyProgs StatUint64 116 MismatchingProgs StatUint64 117 StartTime StatTime 118 // Calls stores statistics for all supported system calls. 119 Calls StatMapStringToCallStats 120 } 121 122 func (stats *Stats) Init() *Stats { 123 stats.TotalCallMismatches.mu = &stats.mu 124 stats.TotalProgs.mu = &stats.mu 125 stats.ExecErrorProgs.mu = &stats.mu 126 stats.FlakyProgs.mu = &stats.mu 127 stats.MismatchingProgs.mu = &stats.mu 128 stats.StartTime.mu = &stats.mu 129 stats.Calls.mu = &stats.mu 130 return stats 131 } 132 133 func (stats *Stats) MismatchesFound() bool { 134 return stats.TotalCallMismatches.Get() != 0 135 } 136 137 func (stats *Stats) IncCallMismatches(key string) { 138 stats.mu.Lock() 139 defer stats.mu.Unlock() 140 stats.Calls.mapStringToCallStats[key].Mismatches++ 141 stats.TotalCallMismatches.uint64++ 142 } 143 144 // CallStats stores information used to generate statistics for the 145 // system call. 146 type CallStats struct { 147 // Name is the system call name. 148 Name string 149 // Mismatches stores the number of errno mismatches identified in the 150 // verified programs for this system call. 151 Mismatches uint64 152 // Occurrences is the number of times the system call appeared in a 153 // verified program. 154 Occurrences uint64 155 // States stores the kernel return state that caused mismatches. 156 States map[ReturnState]bool 157 } 158 159 func (stats *CallStats) orderedStates() []string { 160 states := stats.States 161 ss := make([]string, 0, len(states)) 162 for s := range states { 163 ss = append(ss, fmt.Sprintf("%q", s)) 164 } 165 sort.Strings(ss) 166 return ss 167 } 168 169 // MakeStats creates a stats object. 170 func MakeStats() *Stats { 171 return (&Stats{ 172 Calls: StatMapStringToCallStats{ 173 mapStringToCallStats: make(mapStringToCallStats), 174 }, 175 }).Init() 176 } 177 178 func (stats *Stats) SetCallInfo(key string, info *CallStats) { 179 stats.Calls.SetCallInfo(key, info) 180 } 181 182 // SetSyscallMask initializes the allowed syscall list. 183 func (stats *Stats) SetSyscallMask(calls map[*prog.Syscall]bool) { 184 stats.StartTime.Set(time.Now()) 185 186 for syscall := range calls { 187 stats.SetCallInfo(syscall.Name, &CallStats{ 188 Name: syscall.Name, 189 States: make(map[ReturnState]bool)}) 190 } 191 } 192 193 // ReportGlobalStats creates a report with statistics about all the 194 // supported system calls for which errno mismatches were identified in 195 // the verified programs, shown in decreasing order. 196 func (stats *Stats) GetTextDescription(deltaTime float64) string { 197 var result strings.Builder 198 199 tc := stats.Calls.totalExecuted() 200 fmt.Fprintf(&result, "total number of mismatches / total number of calls "+ 201 "executed: %d / %d (%0.2f %%)\n\n", 202 stats.TotalCallMismatches.Get(), tc, getPercentage(stats.TotalCallMismatches.Get(), tc)) 203 fmt.Fprintf(&result, "programs / minute: %0.2f\n\n", float64(stats.TotalProgs.Get())/deltaTime) 204 fmt.Fprintf(&result, "true mismatching programs: %d / total number of programs: %d (%0.2f %%)\n", 205 stats.MismatchingProgs.Get(), stats.TotalProgs.Get(), 206 getPercentage(stats.MismatchingProgs.Get(), stats.TotalProgs.Get())) 207 fmt.Fprintf(&result, "flaky programs: %d / total number of programs: %d (%0.2f %%)\n\n", 208 stats.FlakyProgs.Get(), stats.TotalProgs.Get(), getPercentage(stats.FlakyProgs.Get(), stats.TotalProgs.Get())) 209 cs := stats.Calls.orderedStats() 210 for _, c := range cs { 211 fmt.Fprintf(&result, "%s\n", stats.getCallStatsTextDescription(c.Name)) 212 } 213 214 return result.String() 215 } 216 217 // getCallStatsTextDescription creates a report with the current statistics for call. 218 func (stats *Stats) getCallStatsTextDescription(call string) string { 219 totalCallMismatches := stats.TotalCallMismatches.Get() 220 stats.mu.Lock() 221 defer stats.mu.Unlock() 222 syscallStat, ok := stats.Calls.mapStringToCallStats[call] 223 if !ok { 224 return "" 225 } 226 syscallName, mismatches, occurrences := syscallStat.Name, syscallStat.Mismatches, syscallStat.Occurrences 227 return fmt.Sprintf("statistics for %s:\n"+ 228 "\t↳ mismatches of %s / occurrences of %s: %d / %d (%0.2f %%)\n"+ 229 "\t↳ mismatches of %s / total number of mismatches: "+ 230 "%d / %d (%0.2f %%)\n"+ 231 "\t↳ %d distinct states identified: %v\n", syscallName, syscallName, syscallName, mismatches, occurrences, 232 getPercentage(mismatches, occurrences), syscallName, mismatches, totalCallMismatches, 233 getPercentage(mismatches, totalCallMismatches), 234 len(syscallStat.States), syscallStat.orderedStates()) 235 } 236 237 func getPercentage(value, total uint64) float64 { 238 return float64(value) / float64(total) * 100 239 }