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  }