Skip to content

Commit b578484

Browse files
committed
pkg/manager: export programs + coverage
1 parent 29521f7 commit b578484

File tree

3 files changed

+90
-21
lines changed

3 files changed

+90
-21
lines changed

pkg/cover/html.go

Lines changed: 71 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"bufio"
88
"bytes"
99
_ "embed"
10+
"encoding/base64"
1011
"encoding/csv"
1112
"encoding/json"
1213
"fmt"
@@ -16,6 +17,7 @@ import (
1617
"math"
1718
"os"
1819
"path/filepath"
20+
"slices"
1921
"sort"
2022
"strconv"
2123
"strings"
@@ -178,7 +180,7 @@ func fileLineContents(file *file, lines [][]byte) lineCoverExport {
178180

179181
func (rg *ReportGenerator) DoRawCoverFiles(w io.Writer, params HandlerParams) error {
180182
progs := fixUpPCs(params.Progs, params.Filter)
181-
if err := rg.symbolizePCs(uniquePCs(progs)); err != nil {
183+
if err := rg.symbolizePCs(uniquePCs(progs...)); err != nil {
182184
return err
183185
}
184186

@@ -215,6 +217,24 @@ type CoverageInfo struct {
215217
PC uint64 `json:"pc"`
216218
}
217219

220+
func makeCoverageInfo(frame *backend.Frame) *CoverageInfo {
221+
endCol := frame.Range.EndCol
222+
if endCol == backend.LineEnd {
223+
endCol = -1
224+
}
225+
return &CoverageInfo{
226+
FilePath: frame.Name,
227+
FuncName: frame.FuncName,
228+
StartLine: frame.Range.StartLine,
229+
StartCol: frame.Range.StartCol,
230+
EndLine: frame.Range.EndLine,
231+
EndCol: endCol,
232+
HitCount: 1, // We know it is at least 1.
233+
Inline: frame.Inline,
234+
PC: frame.PC,
235+
}
236+
}
237+
218238
// DoCoverJSONL is a handler for "/cover?jsonl=1".
219239
func (rg *ReportGenerator) DoCoverJSONL(w io.Writer, params HandlerParams) error {
220240
if rg.CallbackPoints != nil {
@@ -223,7 +243,7 @@ func (rg *ReportGenerator) DoCoverJSONL(w io.Writer, params HandlerParams) error
223243
}
224244
}
225245
progs := fixUpPCs(params.Progs, params.Filter)
226-
if err := rg.symbolizePCs(uniquePCs(progs)); err != nil {
246+
if err := rg.symbolizePCs(uniquePCs(progs...)); err != nil {
227247
return err
228248
}
229249
pcProgCount := make(map[uint64]int)
@@ -234,28 +254,62 @@ func (rg *ReportGenerator) DoCoverJSONL(w io.Writer, params HandlerParams) error
234254
}
235255
encoder := json.NewEncoder(w)
236256
for _, frame := range rg.Frames {
237-
endCol := frame.Range.EndCol
238-
if endCol == backend.LineEnd {
239-
endCol = -1
240-
}
241-
covInfo := &CoverageInfo{
242-
FilePath: frame.Name,
243-
FuncName: frame.FuncName,
244-
StartLine: frame.Range.StartLine,
245-
StartCol: frame.Range.StartCol,
246-
EndLine: frame.Range.EndLine,
247-
EndCol: endCol,
248-
HitCount: pcProgCount[frame.PC],
249-
Inline: frame.Inline,
250-
PC: frame.PC,
251-
}
257+
covInfo := makeCoverageInfo(frame)
258+
covInfo.HitCount = pcProgCount[frame.PC]
252259
if err := encoder.Encode(covInfo); err != nil {
253260
return fmt.Errorf("failed to json.Encode(): %w", err)
254261
}
255262
}
256263
return nil
257264
}
258265

266+
type ProgramCoverage struct {
267+
Program string
268+
CoveredLines []*CoverageInfo
269+
}
270+
271+
// DoCoverPrograms returns the corpus programs with the associated coverage.
272+
// The result is a jsonl stream.
273+
// Each record represents the single program and multiple CoverageInfo records.
274+
func (rg *ReportGenerator) DoCoverPrograms(w io.Writer, params HandlerParams) error {
275+
if rg.CallbackPoints != nil {
276+
if err := rg.symbolizePCs(rg.CallbackPoints); err != nil {
277+
return fmt.Errorf("failed to symbolize PCs(): %w", err)
278+
}
279+
}
280+
pcToFrames := map[uint64][]*backend.Frame{}
281+
for _, frame := range rg.Frames {
282+
pcToFrames[frame.PC] = append(pcToFrames[frame.PC], frame)
283+
}
284+
encoder := json.NewEncoder(w)
285+
for prog := range slices.Values(params.Progs) {
286+
var covInfos []*CoverageInfo
287+
for pc := range slices.Values(uniquePCs(prog)) {
288+
for frame := range slices.Values(pcToFrames[pc]) {
289+
covInfos = append(covInfos, makeCoverageInfo(frame))
290+
}
291+
}
292+
293+
progBase64 := bytes.Buffer{}
294+
base64Enc := base64.NewEncoder(base64.StdEncoding, &progBase64)
295+
if _, err := base64Enc.Write([]byte(prog.Data)); err != nil {
296+
return fmt.Errorf("base64Enc.Write: %w", err)
297+
}
298+
base64Enc.Close()
299+
300+
if err := encoder.Encode(struct {
301+
ProgBase64 string
302+
Coverage []*CoverageInfo
303+
}{
304+
ProgBase64: progBase64.String(),
305+
Coverage: covInfos,
306+
}); err != nil {
307+
return fmt.Errorf("encoder.Encode: %w", err)
308+
}
309+
}
310+
return nil
311+
}
312+
259313
func (rg *ReportGenerator) DoRawCover(w io.Writer, params HandlerParams) error {
260314
progs := fixUpPCs(params.Progs, params.Filter)
261315
var pcs []uint64

pkg/cover/report.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func GetPCBase(cfg *mgrconfig.Config) (uint64, error) {
3434
}
3535

3636
func MakeReportGenerator(cfg *mgrconfig.Config, subsystem []mgrconfig.Subsystem,
37-
modules []*vminfo.KernelModule, rawCover bool) (*ReportGenerator, error) {
37+
modules []*vminfo.KernelModule, rawCover bool) (*ReportGenerator, error) {
3838
impl, err := backend.Make(cfg.SysTarget, cfg.Type, cfg.KernelObj,
3939
cfg.KernelSrc, cfg.KernelBuildSrc, cfg.AndroidSplitBuild, cfg.ModuleObj, modules)
4040
if err != nil {
@@ -81,7 +81,7 @@ type line struct {
8181
type fileMap map[string]*file
8282

8383
func (rg *ReportGenerator) prepareFileMap(progs []Prog, force, debug bool) (fileMap, error) {
84-
if err := rg.symbolizePCs(uniquePCs(progs)); err != nil {
84+
if err := rg.symbolizePCs(uniquePCs(progs...)); err != nil {
8585
return nil, err
8686
}
8787
files := make(fileMap)
@@ -183,11 +183,11 @@ func coverageCallbackMismatch(debug bool, numPCs int, unmatchedPCs map[uint64]bo
183183
}
184184
}
185185
return fmt.Errorf("%d out of %d PCs returned by kcov do not have matching coverage callbacks."+
186-
" Check the discoverModules() code. Use ?force=1 to disable this message.%s",
186+
" Check the discoverModules() code. Use ?force=1 to disable this message.%s",
187187
len(unmatchedPCs), numPCs, debugStr)
188188
}
189189

190-
func uniquePCs(progs []Prog) []uint64 {
190+
func uniquePCs(progs ...Prog) []uint64 {
191191
PCs := make(map[uint64]bool)
192192
for _, p := range progs {
193193
for _, pc := range p.PCs {

pkg/manager/http.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ func (serv *HTTPServer) Serve(ctx context.Context) error {
8686
handle("/corpus", serv.httpCorpus)
8787
handle("/corpus.db", serv.httpDownloadCorpus)
8888
handle("/cover", serv.httpCover)
89+
handle("/coverprogs", serv.httpPrograms)
8990
handle("/debuginput", serv.httpDebugInput)
9091
handle("/file", serv.httpFile)
9192
handle("/filecover", serv.httpFileCover)
@@ -444,6 +445,7 @@ const (
444445
DoRawCover
445446
DoFilterPCs
446447
DoCoverJSONL
448+
DoCoverPrograms
447449
)
448450

449451
func (serv *HTTPServer) httpCover(w http.ResponseWriter, r *http.Request) {
@@ -458,6 +460,18 @@ func (serv *HTTPServer) httpCover(w http.ResponseWriter, r *http.Request) {
458460
serv.httpCoverCover(w, r, DoHTML)
459461
}
460462

463+
func (serv *HTTPServer) httpPrograms(w http.ResponseWriter, r *http.Request) {
464+
if !serv.Cfg.Cover {
465+
http.Error(w, "coverage is not enabled", http.StatusInternalServerError)
466+
return
467+
}
468+
if r.FormValue("jsonl") != "1" {
469+
http.Error(w, "only ?jsonl=1 param is supported", http.StatusBadRequest)
470+
return
471+
}
472+
serv.httpCoverCover(w, r, DoCoverPrograms)
473+
}
474+
461475
func (serv *HTTPServer) httpSubsystemCover(w http.ResponseWriter, r *http.Request) {
462476
if !serv.Cfg.Cover {
463477
serv.httpCoverFallback(w, r)
@@ -577,6 +591,7 @@ func (serv *HTTPServer) httpCoverCover(w http.ResponseWriter, r *http.Request, f
577591
DoRawCover: {rg.DoRawCover, ctTextPlain},
578592
DoFilterPCs: {rg.DoFilterPCs, ctTextPlain},
579593
DoCoverJSONL: {rg.DoCoverJSONL, ctApplicationJSON},
594+
DoCoverPrograms: {rg.DoCoverPrograms, ctApplicationJSON},
580595
}
581596

582597
if ct := flagToFunc[funcFlag].contentType; ct != "" {

0 commit comments

Comments
 (0)