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