Skip to content

Commit 3c2cdd0

Browse files
committed
pkg/manager: store titles stat in the crash dir
1 parent 26c9c44 commit 3c2cdd0

File tree

3 files changed

+167
-0
lines changed

3 files changed

+167
-0
lines changed

pkg/manager/crash.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ func (cs *CrashStore) SaveCrash(crash *Crash) (bool, error) {
9191
writeOrRemove("tag", []byte(cs.Tag))
9292
writeOrRemove("report", report.MergeReportBytes(reps))
9393
writeOrRemove("machineInfo", crash.MachineInfo)
94+
if err := report.AddTitleStat(filepath.Join(dir, "title-stat"), reps); err != nil {
95+
return false, fmt.Errorf("report.AddTitleStat: %w", err)
96+
}
9497

9598
return first, nil
9699
}

pkg/report/title_stat.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright 2025 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 report
5+
6+
import (
7+
"encoding/json"
8+
"errors"
9+
"fmt"
10+
"maps"
11+
"os"
12+
)
13+
14+
func AddTitleStat(file string, reps []*Report) error {
15+
var titles []string
16+
for _, rep := range reps {
17+
titles = append(titles, rep.Title)
18+
}
19+
stat, err := readStatFile(file)
20+
if err != nil {
21+
return fmt.Errorf("readStatFile: %w", err)
22+
}
23+
stat.add(titles)
24+
if err := writeStatFile(file, stat); err != nil {
25+
return fmt.Errorf("writeStatFile: %w", err)
26+
}
27+
return nil
28+
}
29+
30+
func readStatFile(file string) (*titleStat, error) {
31+
stat := &titleStat{}
32+
if _, err := os.Stat(file); errors.Is(err, os.ErrNotExist) {
33+
return stat, nil
34+
}
35+
data, err := os.ReadFile(file)
36+
if err != nil {
37+
return nil, err
38+
}
39+
if len(data) == 0 {
40+
return stat, nil
41+
}
42+
if err := json.Unmarshal(data, stat); err != nil {
43+
return nil, err
44+
}
45+
return stat, nil
46+
}
47+
48+
func writeStatFile(file string, stat *titleStat) error {
49+
data, err := json.MarshalIndent(stat, "", "\t")
50+
if err != nil {
51+
return err
52+
}
53+
if err := os.WriteFile(file, data, 0644); err != nil {
54+
return err
55+
}
56+
return nil
57+
}
58+
59+
type titleStatNodes map[string]*titleStat
60+
61+
type titleStat struct {
62+
Count int
63+
Nodes titleStatNodes
64+
}
65+
66+
func (ts *titleStat) add(reps []string) {
67+
if len(reps) == 0 {
68+
return
69+
}
70+
if ts.Nodes == nil {
71+
ts.Nodes = make(titleStatNodes)
72+
}
73+
if ts.Nodes[reps[0]] == nil {
74+
ts.Nodes[reps[0]] = &titleStat{}
75+
}
76+
ts.Nodes[reps[0]].Count++
77+
ts.Nodes[reps[0]].add(reps[1:])
78+
}
79+
80+
func (ts *titleStat) visit(cb func(int, ...string), titles ...string) {
81+
if len(ts.Nodes) == 0 {
82+
cb(ts.Count, titles...)
83+
return
84+
}
85+
for title := range maps.Keys(ts.Nodes) {
86+
ts.Nodes[title].visit(cb, append(titles, title)...)
87+
}
88+
}

pkg/report/title_stat_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright 2025 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 report
5+
6+
import (
7+
"os"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
func TestAddTitleStat(t *testing.T) {
14+
tests := []struct {
15+
name string
16+
base string
17+
reps [][]*Report
18+
want *titleStat
19+
}{
20+
{
21+
name: "read empty",
22+
want: &titleStat{},
23+
},
24+
{
25+
name: "add single",
26+
reps: [][]*Report{{{Title: "warning 1"}}},
27+
want: &titleStat{
28+
Nodes: titleStatNodes{
29+
"warning 1": {Count: 1},
30+
},
31+
},
32+
},
33+
{
34+
name: "add chain",
35+
reps: [][]*Report{{{Title: "warning 1"}, {Title: "warning 2"}}},
36+
want: &titleStat{
37+
Nodes: titleStatNodes{
38+
"warning 1": {Count: 1,
39+
Nodes: titleStatNodes{
40+
"warning 2": {Count: 1},
41+
},
42+
},
43+
},
44+
},
45+
},
46+
{
47+
name: "add multi chains",
48+
reps: [][]*Report{{{Title: "warning 1"}, {Title: "warning 2"}}, {{Title: "warning 1"}, {Title: "warning 3"}}},
49+
want: &titleStat{
50+
Nodes: titleStatNodes{
51+
"warning 1": {Count: 2,
52+
Nodes: titleStatNodes{
53+
"warning 2": {Count: 1},
54+
"warning 3": {Count: 1},
55+
},
56+
},
57+
},
58+
},
59+
},
60+
}
61+
for _, test := range tests {
62+
t.Run(test.name, func(t *testing.T) {
63+
t.Parallel()
64+
tmpFile := t.TempDir() + "/test.input"
65+
err := os.WriteFile(tmpFile, []byte(test.base), 0644)
66+
assert.NoError(t, err)
67+
for _, reps := range test.reps {
68+
err = AddTitleStat(tmpFile, reps)
69+
assert.NoError(t, err)
70+
}
71+
got, err := ReadStatFile(tmpFile)
72+
assert.NoError(t, err)
73+
assert.Equal(t, test.want, got)
74+
})
75+
}
76+
}

0 commit comments

Comments
 (0)