GoPLS Viewer

Home|gopls/cover/profile.go
1// Copyright 2013 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package cover provides support for parsing coverage profiles
6// generated by "go test -coverprofile=cover.out".
7package cover // import "golang.org/x/tools/cover"
8
9import (
10    "bufio"
11    "errors"
12    "fmt"
13    "io"
14    "math"
15    "os"
16    "sort"
17    "strconv"
18    "strings"
19)
20
21// Profile represents the profiling data for a specific file.
22type Profile struct {
23    FileName string
24    Mode     string
25    Blocks   []ProfileBlock
26}
27
28// ProfileBlock represents a single block of profiling data.
29type ProfileBlock struct {
30    StartLineStartCol int
31    EndLineEndCol     int
32    NumStmtCount      int
33}
34
35type byFileName []*Profile
36
37func (p byFileNameLen() int           { return len(p) }
38func (p byFileNameLess(ij intbool { return p[i].FileName < p[j].FileName }
39func (p byFileNameSwap(ij int)      { p[i], p[j] = p[j], p[i] }
40
41// ParseProfiles parses profile data in the specified file and returns a
42// Profile for each source file described therein.
43func ParseProfiles(fileName string) ([]*Profileerror) {
44    pferr := os.Open(fileName)
45    if err != nil {
46        return nilerr
47    }
48    defer pf.Close()
49    return ParseProfilesFromReader(pf)
50}
51
52// ParseProfilesFromReader parses profile data from the Reader and
53// returns a Profile for each source file described therein.
54func ParseProfilesFromReader(rd io.Reader) ([]*Profileerror) {
55    // First line is "mode: foo", where foo is "set", "count", or "atomic".
56    // Rest of file is in the format
57    //    encoding/base64/base64.go:34.44,37.40 3 1
58    // where the fields are: name.go:line.column,line.column numberOfStatements count
59    files := make(map[string]*Profile)
60    s := bufio.NewScanner(rd)
61    mode := ""
62    for s.Scan() {
63        line := s.Text()
64        if mode == "" {
65            const p = "mode: "
66            if !strings.HasPrefix(linep) || line == p {
67                return nilfmt.Errorf("bad mode line: %v"line)
68            }
69            mode = line[len(p):]
70            continue
71        }
72        fnberr := parseLine(line)
73        if err != nil {
74            return nilfmt.Errorf("line %q doesn't match expected format: %v"lineerr)
75        }
76        p := files[fn]
77        if p == nil {
78            p = &Profile{
79                FileNamefn,
80                Mode:     mode,
81            }
82            files[fn] = p
83        }
84        p.Blocks = append(p.Blocksb)
85    }
86    if err := s.Err(); err != nil {
87        return nilerr
88    }
89    for _p := range files {
90        sort.Sort(blocksByStart(p.Blocks))
91        // Merge samples from the same location.
92        j := 1
93        for i := 1i < len(p.Blocks); i++ {
94            b := p.Blocks[i]
95            last := p.Blocks[j-1]
96            if b.StartLine == last.StartLine &&
97                b.StartCol == last.StartCol &&
98                b.EndLine == last.EndLine &&
99                b.EndCol == last.EndCol {
100                if b.NumStmt != last.NumStmt {
101                    return nilfmt.Errorf("inconsistent NumStmt: changed from %d to %d"last.NumStmtb.NumStmt)
102                }
103                if mode == "set" {
104                    p.Blocks[j-1].Count |= b.Count
105                } else {
106                    p.Blocks[j-1].Count += b.Count
107                }
108                continue
109            }
110            p.Blocks[j] = b
111            j++
112        }
113        p.Blocks = p.Blocks[:j]
114    }
115    // Generate a sorted slice.
116    profiles := make([]*Profile0len(files))
117    for _profile := range files {
118        profiles = append(profilesprofile)
119    }
120    sort.Sort(byFileName(profiles))
121    return profilesnil
122}
123
124// parseLine parses a line from a coverage file.
125// It is equivalent to the regex
126// ^(.+):([0-9]+)\.([0-9]+),([0-9]+)\.([0-9]+) ([0-9]+) ([0-9]+)$
127//
128// However, it is much faster: https://golang.org/cl/179377
129func parseLine(l string) (fileName stringblock ProfileBlockerr error) {
130    end := len(l)
131
132    b := ProfileBlock{}
133    b.Countenderr = seekBack(l' 'end"Count")
134    if err != nil {
135        return ""berr
136    }
137    b.NumStmtenderr = seekBack(l' 'end"NumStmt")
138    if err != nil {
139        return ""berr
140    }
141    b.EndColenderr = seekBack(l'.'end"EndCol")
142    if err != nil {
143        return ""berr
144    }
145    b.EndLineenderr = seekBack(l','end"EndLine")
146    if err != nil {
147        return ""berr
148    }
149    b.StartColenderr = seekBack(l'.'end"StartCol")
150    if err != nil {
151        return ""berr
152    }
153    b.StartLineenderr = seekBack(l':'end"StartLine")
154    if err != nil {
155        return ""berr
156    }
157    fn := l[0:end]
158    if fn == "" {
159        return ""berrors.New("a FileName cannot be blank")
160    }
161    return fnbnil
162}
163
164// seekBack searches backwards from end to find sep in l, then returns the
165// value between sep and end as an integer.
166// If seekBack fails, the returned error will reference what.
167func seekBack(l stringsep byteend intwhat string) (value intnextSep interr error) {
168    // Since we're seeking backwards and we know only ASCII is legal for these values,
169    // we can ignore the possibility of non-ASCII characters.
170    for start := end - 1start >= 0start-- {
171        if l[start] == sep {
172            ierr := strconv.Atoi(l[start+1 : end])
173            if err != nil {
174                return 00fmt.Errorf("couldn't parse %q: %v"whaterr)
175            }
176            if i < 0 {
177                return 00fmt.Errorf("negative values are not allowed for %s, found %d"whati)
178            }
179            return istartnil
180        }
181    }
182    return 00fmt.Errorf("couldn't find a %s before %s"string(sep), what)
183}
184
185type blocksByStart []ProfileBlock
186
187func (b blocksByStartLen() int      { return len(b) }
188func (b blocksByStartSwap(ij int) { b[i], b[j] = b[j], b[i] }
189func (b blocksByStartLess(ij intbool {
190    bibj := b[i], b[j]
191    return bi.StartLine < bj.StartLine || bi.StartLine == bj.StartLine && bi.StartCol < bj.StartCol
192}
193
194// Boundary represents the position in a source file of the beginning or end of a
195// block as reported by the coverage profile. In HTML mode, it will correspond to
196// the opening or closing of a <span> tag and will be used to colorize the source
197type Boundary struct {
198    Offset int     // Location as a byte offset in the source file.
199    Start  bool    // Is this the start of a block?
200    Count  int     // Event count from the cover profile.
201    Norm   float64 // Count normalized to [0..1].
202    Index  int     // Order in input file.
203}
204
205// Boundaries returns a Profile as a set of Boundary objects within the provided src.
206func (p *ProfileBoundaries(src []byte) (boundaries []Boundary) {
207    // Find maximum count.
208    max := 0
209    for _b := range p.Blocks {
210        if b.Count > max {
211            max = b.Count
212        }
213    }
214    // Divisor for normalization.
215    divisor := math.Log(float64(max))
216
217    // boundary returns a Boundary, populating the Norm field with a normalized Count.
218    index := 0
219    boundary := func(offset intstart boolcount intBoundary {
220        b := Boundary{OffsetoffsetStartstartCountcountIndexindex}
221        index++
222        if !start || count == 0 {
223            return b
224        }
225        if max <= 1 {
226            b.Norm = 0.8 // Profile is in"set" mode; we want a heat map. Use cov8 in the CSS.
227        } else if count > 0 {
228            b.Norm = math.Log(float64(count)) / divisor
229        }
230        return b
231    }
232
233    linecol := 12 // TODO: Why is this 2?
234    for sibi := 00si < len(src) && bi < len(p.Blocks); {
235        b := p.Blocks[bi]
236        if b.StartLine == line && b.StartCol == col {
237            boundaries = append(boundariesboundary(sitrueb.Count))
238        }
239        if b.EndLine == line && b.EndCol == col || line > b.EndLine {
240            boundaries = append(boundariesboundary(sifalse0))
241            bi++
242            continue // Don't advance through src; maybe the next block starts here.
243        }
244        if src[si] == '\n' {
245            line++
246            col = 0
247        }
248        col++
249        si++
250    }
251    sort.Sort(boundariesByPos(boundaries))
252    return
253}
254
255type boundariesByPos []Boundary
256
257func (b boundariesByPosLen() int      { return len(b) }
258func (b boundariesByPosSwap(ij int) { b[i], b[j] = b[j], b[i] }
259func (b boundariesByPosLess(ij intbool {
260    if b[i].Offset == b[j].Offset {
261        // Boundaries at the same offset should be ordered according to
262        // their original position.
263        return b[i].Index < b[j].Index
264    }
265    return b[i].Offset < b[j].Offset
266}
267
MembersX
blocksByStart.Less.b
Profile.Boundaries.si
byFileName.Less.p
parseLine.end
Boundary.Offset
Boundary.Norm
ParseProfilesFromReader.RangeStmt_2339.BlockStmt.i
boundariesByPos.Len
strconv
ProfileBlock
blocksByStart.Len
boundariesByPos.Len.b
boundariesByPos.Swap
ParseProfiles.err
byFileName.Less
byFileName.Swap.i
blocksByStart.Swap.j
ParseProfilesFromReader.BlockStmt.err
ParseProfilesFromReader.RangeStmt_3082.profile
Profile.Boundaries.src
Profile.Boundaries.RangeStmt_6103.b
bufio
ParseProfilesFromReader
ParseProfilesFromReader.err
boundariesByPos.Less.i
parseLine.fileName
seekBack
blocksByStart.Swap.i
parseLine.err
blocksByStart.Len.b
blocksByStart.Swap
boundariesByPos.Less.j
Profile.Mode
byFileName.Less.j
ParseProfiles
boundariesByPos.Less.b
boundariesByPos.Less
fmt
ParseProfilesFromReader.RangeStmt_2339.p
seekBack.end
ProfileBlock.StartLine
ParseProfilesFromReader.BlockStmt.b
Boundary
Profile.Blocks
ProfileBlock.NumStmt
Profile.Boundaries.BlockStmt.b
seekBack.value
Profile.Boundaries.bi
ProfileBlock.Count
blocksByStart.Swap.b
ParseProfilesFromReader.s
parseLine.b
Profile.Boundaries.max
Profile.Boundaries.line
ParseProfilesFromReader.BlockStmt.fn
blocksByStart
Profile.Boundaries.boundaries
boundariesByPos.Swap.b
ParseProfiles.pf
seekBack.what
ParseProfiles.fileName
Profile.Boundaries
byFileName.Swap.j
parseLine
seekBack.BlockStmt.BlockStmt.err
Boundary.Count
os
boundariesByPos.Swap.i
boundariesByPos.Swap.j
ParseProfilesFromReader.files
parseLine.l
parseLine.block
seekBack.err
io
Profile.FileName
byFileName.Len.p
ParseProfilesFromReader.BlockStmt.line
errors
blocksByStart.Less
seekBack.l
seekBack.sep
seekBack.BlockStmt.BlockStmt.i
blocksByStart.Less.i
ProfileBlock.StartCol
byFileName.Less.i
ParseProfilesFromReader.mode
Boundary.Start
Profile.Boundaries.p
Profile.Boundaries.divisor
Profile.Boundaries.index
strings
byFileName.Swap
seekBack.nextSep
ParseProfilesFromReader.BlockStmt.BlockStmt.p
sort
Profile
ParseProfilesFromReader.RangeStmt_2339.BlockStmt.j
ProfileBlock.EndCol
ParseProfilesFromReader.rd
Boundary.Index
boundariesByPos
ProfileBlock.EndLine
byFileName.Len
Profile.Boundaries.col
byFileName.Swap.p
ParseProfilesFromReader.profiles
blocksByStart.Less.j
math
byFileName
Members
X