@@ -17,6 +17,7 @@ import (
1717 "github.com/google/syzkaller/pkg/coveragedb/spannerclient"
1818 _ "github.com/google/syzkaller/pkg/subsystem/lists"
1919 "golang.org/x/exp/maps"
20+ "golang.org/x/sync/errgroup"
2021 "google.golang.org/api/iterator"
2122)
2223
@@ -115,6 +116,12 @@ type fileCoverageWithDetails struct {
115116 Subsystems []string
116117}
117118
119+ type fileCoverageWithLineInfo struct {
120+ fileCoverageWithDetails
121+ LinesInstrumented []int64
122+ HitCounts []int64
123+ }
124+
118125type pageColumnTarget struct {
119126 TimePeriod coveragedb.TimePeriod
120127 Commit string
@@ -157,18 +164,17 @@ func filesCoverageToTemplateData(fCov []*fileCoverageWithDetails) *templateHeatm
157164 return & res
158165}
159166
160- func filesCoverageWithDetailsStmt (ns , subsystem , manager string , timePeriod coveragedb.TimePeriod ) spanner.Statement {
167+ func filesCoverageWithDetailsStmt (ns , subsystem , manager string , timePeriod coveragedb.TimePeriod , withLines bool ,
168+ ) spanner.Statement {
161169 if manager == "" {
162170 manager = "*"
163171 }
172+ selectColumns := "commit, instrumented, covered, files.filepath, subsystems"
173+ if withLines {
174+ selectColumns += ", linesinstrumented, hitcounts"
175+ }
164176 stmt := spanner.Statement {
165- SQL : `
166- select
167- commit,
168- instrumented,
169- covered,
170- files.filepath,
171- subsystems
177+ SQL : "select " + selectColumns + `
172178from merge_history
173179 join files
174180 on merge_history.session = files.session
@@ -187,37 +193,143 @@ where
187193 stmt .SQL += " and $5=ANY(subsystems)"
188194 stmt .Params ["p5" ] = subsystem
189195 }
196+ stmt .SQL += "\n order by files.filepath"
190197 return stmt
191198}
192199
193- func filesCoverageWithDetails (ctx context.Context , projectID string , scope * SelectScope ,
194- ) ([]* fileCoverageWithDetails , error ) {
195- client , err := spannerclient .NewClient (ctx , projectID )
200+ func readCoverage (iterManager spannerclient.RowIterator ) ([]* fileCoverageWithDetails , error ) {
201+ res := []* fileCoverageWithDetails {}
202+ ch := make (chan * fileCoverageWithDetails )
203+ var err error
204+ go func () {
205+ defer close (ch )
206+ err = readIterToChan (context .Background (), iterManager , ch )
207+ }()
208+ for fc := range ch {
209+ res = append (res , fc )
210+ }
196211 if err != nil {
197- return nil , fmt .Errorf ("spanner.NewClient() failed : %s " , err . Error () )
212+ return nil , fmt .Errorf ("readIterToChan : %w " , err )
198213 }
199- defer client .Close ()
214+ return res , nil
215+ }
200216
217+ // Unique coverage from specific manager is more expensive to get.
218+ // We get unique coverage comparing manager and total coverage on the AppEngine side.
219+ func readCoverageUniq (full , mgr spannerclient.RowIterator ,
220+ ) ([]* fileCoverageWithDetails , error ) {
221+ eg , ctx := errgroup .WithContext (context .Background ())
222+ fullCh := make (chan * fileCoverageWithLineInfo )
223+ eg .Go (func () error {
224+ defer close (fullCh )
225+ return readIterToChan (ctx , full , fullCh )
226+ })
227+ partCh := make (chan * fileCoverageWithLineInfo )
228+ eg .Go (func () error {
229+ defer close (partCh )
230+ return readIterToChan (ctx , mgr , partCh )
231+ })
201232 res := []* fileCoverageWithDetails {}
202- for _ , timePeriod := range scope .Periods {
203- stmt := filesCoverageWithDetailsStmt (scope .Ns , scope .Subsystem , scope .Manager , timePeriod )
204- iter := client .Single ().Query (ctx , stmt )
205- defer iter .Stop ()
206- for {
207- row , err := iter .Next ()
208- if err == iterator .Done {
209- break
233+ eg .Go (func () error {
234+ partCov := <- partCh
235+ for fullCov := range fullCh {
236+ if partCov == nil || partCov .Filepath > fullCov .Filepath {
237+ // No pair for the file in full aggregation is available.
238+ cov := fullCov .fileCoverageWithDetails
239+ cov .Covered = 0
240+ res = append (res , & cov )
241+ continue
242+ }
243+ if partCov .Filepath == fullCov .Filepath {
244+ if len (partCov .LinesInstrumented ) > len (fullCov .LinesInstrumented ) ||
245+ len (partCov .HitCounts ) > len (fullCov .HitCounts ) ||
246+ partCov .Commit != fullCov .Commit {
247+ return fmt .Errorf ("db record for file %s don't match" , fullCov .Filepath )
248+ }
249+ res = append (res , uniqCoverage (fullCov , partCov ))
250+ partCov = <- partCh
251+ continue
210252 }
253+ // Partial coverage is a subset of full coverage.
254+ // File can't exist only in partial set.
255+ return fmt .Errorf ("currupted db, file %s can't exist" , partCov .Filepath )
256+ }
257+ return nil
258+ })
259+ if err := eg .Wait (); err != nil {
260+ return nil , fmt .Errorf ("eg.Wait: %w" , err )
261+ }
262+ return res , nil
263+ }
264+
265+ func uniqCoverage (full , partial * fileCoverageWithLineInfo ) * fileCoverageWithDetails {
266+ res := full .fileCoverageWithDetails // Use Instrumented count from full aggregation.
267+ res .Covered = 0 // We're recalculating only the covered lines.
268+ fullCov := map [int64 ]int64 {}
269+ for i , ln := range full .LinesInstrumented {
270+ fullCov [ln ] = full .HitCounts [i ]
271+ }
272+ for i , ln := range partial .LinesInstrumented {
273+ if hitCount , exist := fullCov [ln ]; exist && hitCount > 0 && hitCount == partial .HitCounts [i ] {
274+ res .Covered ++
275+ }
276+ }
277+ return & res
278+ }
279+
280+ func readIterToChan [K fileCoverageWithLineInfo | fileCoverageWithDetails ](
281+ ctx context.Context , iter spannerclient.RowIterator , ch chan <- * K ) error {
282+ for {
283+ row , err := iter .Next ()
284+ if err == iterator .Done {
285+ break
286+ }
287+ if err != nil {
288+ return fmt .Errorf ("iter.Next: %w" , err )
289+ }
290+ var r K
291+ if err = row .ToStruct (& r ); err != nil {
292+ return fmt .Errorf ("row.ToStruct: %w" , err )
293+ }
294+ select {
295+ case ch <- & r :
296+ case <- ctx .Done ():
297+ return nil
298+ }
299+ }
300+ return nil
301+ }
302+
303+ func filesCoverageWithDetails (
304+ ctx context.Context , client spannerclient.SpannerClient , scope * SelectScope , onlyUnique bool ,
305+ ) ([]* fileCoverageWithDetails , error ) {
306+ var res []* fileCoverageWithDetails
307+ for _ , timePeriod := range scope .Periods {
308+ needLinesDetails := onlyUnique
309+ iterManager := client .Single ().Query (ctx ,
310+ filesCoverageWithDetailsStmt (scope .Ns , scope .Subsystem , scope .Manager , timePeriod , needLinesDetails ))
311+ defer iterManager .Stop ()
312+
313+ var err error
314+ var periodRes []* fileCoverageWithDetails
315+ if onlyUnique {
316+ iterAll := client .Single ().Query (ctx ,
317+ filesCoverageWithDetailsStmt (scope .Ns , scope .Subsystem , "" , timePeriod , needLinesDetails ))
318+ defer iterAll .Stop ()
319+ periodRes , err = readCoverageUniq (iterAll , iterManager )
211320 if err != nil {
212- return nil , fmt .Errorf ("failed to iter.Next() spanner DB : %w" , err )
321+ return nil , fmt .Errorf ("uniqueFilesCoverageWithDetails : %w" , err )
213322 }
214- var r fileCoverageWithDetails
215- if err = row .ToStruct (& r ); err != nil {
216- return nil , fmt .Errorf ("failed to row.ToStruct() spanner DB: %w" , err )
323+ } else {
324+ periodRes , err = readCoverage (iterManager )
325+ if err != nil {
326+ return nil , fmt .Errorf ("readCoverage: %w" , err )
217327 }
328+ }
329+ for _ , r := range periodRes {
218330 r .TimePeriod = timePeriod
219- res = append (res , & r )
220331 }
332+ res = append (res , periodRes ... )
221333 }
222334 return res , nil
223335}
@@ -252,9 +364,10 @@ type SelectScope struct {
252364 Periods []coveragedb.TimePeriod
253365}
254366
255- func DoHeatMapStyleBodyJS (ctx context.Context , projectID string , scope * SelectScope , sss , managers []string ,
367+ func DoHeatMapStyleBodyJS (
368+ ctx context.Context , client spannerclient.SpannerClient , scope * SelectScope , onlyUnique bool , sss , managers []string ,
256369) (template.CSS , template.HTML , template.HTML , error ) {
257- covAndDates , err := filesCoverageWithDetails (ctx , projectID , scope )
370+ covAndDates , err := filesCoverageWithDetails (ctx , client , scope , onlyUnique )
258371 if err != nil {
259372 return "" , "" , "" , fmt .Errorf ("failed to filesCoverageWithDetails: %w" , err )
260373 }
@@ -264,9 +377,10 @@ func DoHeatMapStyleBodyJS(ctx context.Context, projectID string, scope *SelectSc
264377 return stylesBodyJSTemplate (templData )
265378}
266379
267- func DoSubsystemsHeatMapStyleBodyJS (ctx context.Context , projectID string , scope * SelectScope , sss , managers []string ,
380+ func DoSubsystemsHeatMapStyleBodyJS (
381+ ctx context.Context , client spannerclient.SpannerClient , scope * SelectScope , onlyUnique bool , sss , managers []string ,
268382) (template.CSS , template.HTML , template.HTML , error ) {
269- covWithDetails , err := filesCoverageWithDetails (ctx , projectID , scope )
383+ covWithDetails , err := filesCoverageWithDetails (ctx , client , scope , onlyUnique )
270384 if err != nil {
271385 panic (err )
272386 }
0 commit comments