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  }