github.com/nya3jp/tast@v0.0.0-20230601000426-85c8e4d83a9b/src/go.chromium.org/tast/core/internal/timing/timing.go (about) 1 // Copyright 2017 The ChromiumOS Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 // Package timing is used to collect and write timing information about a process. 6 package timing 7 8 import ( 9 "bufio" 10 "encoding/json" 11 "fmt" 12 "io" 13 "strings" 14 "sync" 15 "time" 16 17 "github.com/golang/protobuf/ptypes" 18 "github.com/golang/protobuf/ptypes/timestamp" 19 20 "go.chromium.org/tast/core/errors" 21 "go.chromium.org/tast/core/internal/protocol" 22 ) 23 24 // now is the function to return the current time. This is altered in unit tests. 25 var now = time.Now 26 27 // Log contains nested timing information. 28 type Log struct { 29 // Root is a special root stage containing all stages as its descendants. 30 // Its End should not be called, and its timestamps should be ignored. 31 Root *Stage 32 } 33 34 // NewLog returns a new Log. 35 func NewLog() *Log { 36 return &Log{Root: &Stage{}} 37 } 38 39 // StartTop starts and returns a new top-level stage named name. 40 func (l *Log) StartTop(name string) *Stage { 41 return l.Root.StartChild(name) 42 } 43 44 // Empty returns true if l doesn't contain any stages. 45 func (l *Log) Empty() bool { 46 l.Root.mu.Lock() 47 defer l.Root.mu.Unlock() 48 49 return len(l.Root.Children) == 0 50 } 51 52 // WritePretty writes timing information to w as JSON, consisting of an array 53 // of stages, each represented by an array consisting of the stage's duration, name, 54 // and an optional array of child stages. 55 // 56 // Note that this format is lossy and differs from that used by json.Marshaler. 57 // 58 // Output is intended to improve human readability: 59 // 60 // [[4.000, "stage0", [ 61 // [3.000, "stage1", [ 62 // [1.000, "stage2"], 63 // [2.000, "stage3"]]], 64 // [1.000, "stage4"]]], 65 // [0.531, "stage5"]] 66 func (l *Log) WritePretty(w io.Writer) error { 67 l.Root.mu.Lock() 68 defer l.Root.mu.Unlock() 69 70 // Use a bufio.Writer to avoid any further writes after an error is encountered. 71 bw := bufio.NewWriter(w) 72 73 io.WriteString(bw, "[") 74 for i, s := range l.Root.Children { 75 // The first top-level stage is on the same line as the opening '['. 76 // Indent its children and all subsequent stages by a single space so they line up. 77 var indent string 78 if i > 0 { 79 indent = " " 80 } 81 if err := s.writePretty(bw, indent, " ", i == len(l.Root.Children)-1); err != nil { 82 return err 83 } 84 } 85 86 io.WriteString(bw, "]\n") 87 return bw.Flush() // returns first error encountered during earlier writes 88 } 89 90 // Proto returns a protobuf presentation of Log. 91 func (l *Log) Proto() (*protocol.TimingLog, error) { 92 r, err := l.Root.Proto() 93 if err != nil { 94 return nil, err 95 } 96 return &protocol.TimingLog{Root: r}, nil 97 } 98 99 // jsonLog represents the JSON schema of Log. 100 type jsonLog struct { 101 Stages []*Stage `json:"stages"` 102 } 103 104 // MarshalJSON marshals Log as JSON. 105 func (l *Log) MarshalJSON() ([]byte, error) { 106 return json.Marshal(&jsonLog{Stages: l.Root.Children}) 107 } 108 109 // UnmarshalJSON unmarshals Log as JSON. 110 func (l *Log) UnmarshalJSON(b []byte) error { 111 var jl jsonLog 112 if err := json.Unmarshal(b, &jl); err != nil { 113 return err 114 } 115 l.Root = &Stage{Children: jl.Stages} 116 return nil 117 } 118 119 var _ json.Marshaler = (*Log)(nil) 120 var _ json.Unmarshaler = (*Log)(nil) 121 122 // Stage represents a discrete unit of work that is being timed. 123 type Stage struct { 124 Name string `json:"name"` 125 StartTime time.Time `json:"startTime"` 126 EndTime time.Time `json:"endTime"` 127 Children []*Stage `json:"children"` 128 129 mu sync.Mutex // protects EndTime and Children 130 } 131 132 // Import imports the stages from o into s, with o's top-level stages inserted as children of s. 133 // An error is returned if s is already ended. 134 func (s *Stage) Import(o *Log) error { 135 s.mu.Lock() 136 defer s.mu.Unlock() 137 138 if !s.EndTime.IsZero() { 139 return errors.New("stage has ended") 140 } 141 142 s.Children = append(s.Children, o.Root.Children...) 143 return nil 144 } 145 146 // StartChild creates and returns a new named timing stage as a child of s. 147 // Stage.End should be called when the stage is completed. 148 func (s *Stage) StartChild(name string) *Stage { 149 s.mu.Lock() 150 defer s.mu.Unlock() 151 152 if !s.EndTime.IsZero() { 153 return nil 154 } 155 156 c := &Stage{ 157 Name: name, 158 StartTime: now(), 159 } 160 s.Children = append(s.Children, c) 161 return c 162 } 163 164 // End ends the stage. Child stages are recursively examined and also ended 165 // (although we expect them to have already been ended). 166 func (s *Stage) End() { 167 // Handle nil receivers returned by the package-level Start function. 168 if s == nil { 169 return 170 } 171 172 s.mu.Lock() 173 defer s.mu.Unlock() 174 175 if !s.EndTime.IsZero() { 176 return 177 } 178 179 for _, c := range s.Children { 180 c.End() 181 } 182 s.EndTime = now() 183 } 184 185 // writePretty writes information about the stage and its children to w as a JSON array. 186 // The first line of output is indented by initialIndent, while any subsequent lines (e.g. 187 // for child stages) are indented by followIndent. last should be true if this is the last 188 // entry in its parent array; otherwise a trailing comma and newline are appended. 189 // The caller is responsible for checking w for errors encountered while writing. 190 func (s *Stage) writePretty(w *bufio.Writer, initialIndent, followIndent string, last bool) error { 191 s.mu.Lock() 192 defer s.mu.Unlock() 193 194 // Start the stage's array. 195 mn, err := json.Marshal(&s.Name) 196 if err != nil { 197 return err 198 } 199 200 var elapsed time.Duration 201 if s.EndTime.IsZero() { 202 elapsed = now().Sub(s.StartTime) 203 } else { 204 elapsed = s.EndTime.Sub(s.StartTime) 205 } 206 fmt.Fprintf(w, "%s[%0.3f, %s", initialIndent, elapsed.Seconds(), mn) 207 208 // Print children in a nested array. 209 if len(s.Children) > 0 { 210 io.WriteString(w, ", [\n") 211 ci := followIndent + strings.Repeat(" ", 8) 212 for i, c := range s.Children { 213 if err := c.writePretty(w, ci, ci, i == len(s.Children)-1); err != nil { 214 return err 215 } 216 } 217 io.WriteString(w, "]") 218 } 219 220 // End the stage's array. 221 io.WriteString(w, "]") 222 if !last { 223 io.WriteString(w, ",\n") 224 } 225 return nil 226 } 227 228 // Proto returns a protobuf presentation of Stage. 229 func (s *Stage) Proto() (*protocol.TimingStage, error) { 230 start, err := timestampProto(s.StartTime) 231 if err != nil { 232 return nil, errors.Wrap(err, "failed to convert StartTime to protobuf") 233 } 234 end, err := timestampProto(s.EndTime) 235 if err != nil { 236 return nil, errors.Wrap(err, "failed to convert EndTime to protobuf") 237 } 238 var children []*protocol.TimingStage 239 for _, c := range s.Children { 240 child, err := c.Proto() 241 if err != nil { 242 return nil, err 243 } 244 children = append(children, child) 245 } 246 return &protocol.TimingStage{ 247 Name: s.Name, 248 StartTime: start, 249 EndTime: end, 250 Children: children, 251 }, nil 252 } 253 254 func timestampProto(t time.Time) (*timestamp.Timestamp, error) { 255 if t.IsZero() { 256 return nil, nil 257 } 258 return ptypes.TimestampProto(t) 259 } 260 261 // LogFromProto constructs Log from its protocol buffer presentation. 262 func LogFromProto(p *protocol.TimingLog) (*Log, error) { 263 s, err := StageFromProto(p.GetRoot()) 264 if err != nil { 265 return nil, err 266 } 267 return &Log{Root: s}, nil 268 } 269 270 // StageFromProto constructs Stage from its protocol buffer presentation. 271 func StageFromProto(p *protocol.TimingStage) (*Stage, error) { 272 var start, end time.Time 273 if ts := p.GetStartTime(); ts != nil { 274 var err error 275 start, err = ptypes.Timestamp(ts) 276 if err != nil { 277 return nil, err 278 } 279 } 280 if ts := p.GetEndTime(); ts != nil { 281 var err error 282 end, err = ptypes.Timestamp(ts) 283 if err != nil { 284 return nil, err 285 } 286 } 287 var children []*Stage 288 for _, c := range p.GetChildren() { 289 child, err := StageFromProto(c) 290 if err != nil { 291 return nil, err 292 } 293 children = append(children, child) 294 } 295 return &Stage{ 296 Name: p.GetName(), 297 StartTime: start, 298 EndTime: end, 299 Children: children, 300 }, nil 301 }