Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(260)

Side by Side Diff: client/logdog/annotee/annotation/annotation.go

Issue 2069283002: milo: fix step duration (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/luci-go@master
Patch Set: simplify step duration computation Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 package annotation 5 package annotation
6 6
7 import ( 7 import (
8 "fmt" 8 "fmt"
9 "sort" 9 "sort"
10 "strconv" 10 "strconv"
11 "strings" 11 "strings"
12 "time"
12 13
13 "github.com/luci/luci-go/common/clock" 14 "github.com/luci/luci-go/common/clock"
14 "github.com/luci/luci-go/common/logdog/types" 15 "github.com/luci/luci-go/common/logdog/types"
15 "github.com/luci/luci-go/common/proto/google" 16 "github.com/luci/luci-go/common/proto/google"
16 "github.com/luci/luci-go/common/proto/milo" 17 "github.com/luci/luci-go/common/proto/milo"
17 ) 18 )
18 19
19 // Callbacks is the set of callbacks that a State may invoke as it processes 20 // Callbacks is the set of callbacks that a State may invoke as it processes
20 // annotations. 21 // annotations.
21 type Callbacks interface { 22 type Callbacks interface {
(...skipping 13 matching lines...) Expand all
35 // and can be serialized to a Milo annotation state protobuf. 36 // and can be serialized to a Milo annotation state protobuf.
36 type State struct { 37 type State struct {
37 // LogNameBase is the base log stream name that is prepeneded to generat ed 38 // LogNameBase is the base log stream name that is prepeneded to generat ed
38 // log streams. 39 // log streams.
39 LogNameBase types.StreamName 40 LogNameBase types.StreamName
40 // Callbacks implements annotation callbacks. It may not be nil. 41 // Callbacks implements annotation callbacks. It may not be nil.
41 Callbacks Callbacks 42 Callbacks Callbacks
42 // Execution is the supplied Execution. If nil, no execution details wil l be 43 // Execution is the supplied Execution. If nil, no execution details wil l be
43 // added to the generated Milo protos. 44 // added to the generated Milo protos.
44 Execution *Execution 45 Execution *Execution
46 // Offline specifies whether parsing happens not at the same time as
47 // emitting. If true and CURRENT_TIMESTAMP annotations are not provided
48 // then step start/end times are left empty.
49 Offline bool
45 // Clock is the clock implementation to use for time information. 50 // Clock is the clock implementation to use for time information.
51 // Defaults to system time.
46 Clock clock.Clock 52 Clock clock.Clock
47 53
48 // stepMap is a map of step name to Step instance. 54 // stepMap is a map of step name to Step instance.
49 // 55 //
50 // If stepMap is nil, the State is considered uninitialized. 56 // If stepMap is nil, the State is considered uninitialized.
51 stepMap map[string]*Step 57 stepMap map[string]*Step
52 steps []*Step 58 steps []*Step
53 rootStep Step 59 rootStep Step
54 // stepCursor is the current cursor step name. This will always point to a 60 // stepCursor is the current cursor step name. This will always point to a
55 // valid Step, falling back to rootStep. 61 // valid Step, falling back to rootStep.
56 stepCursor *Step 62 stepCursor *Step
63 // startedProcessing is true iff processed at least one annotation.
64 startedProcessing bool
57 65
58 » closed bool 66 » // currentTimestamp is time for the next annotation expected in Append.
59 » haltOnFailure bool 67 » currentTimestamp *google.Timestamp
68 » closed bool
69 » haltOnFailure bool
60 } 70 }
61 71
62 // initialize sets of the State's initial state. It will execute exactly once, 72 // initialize sets of the State's initial state. It will execute exactly once,
63 // and must be called by any State methods that access internal variables. 73 // and must be called by any State methods that access internal variables.
64 func (s *State) initialize() { 74 func (s *State) initialize() {
65 if s.stepMap != nil { 75 if s.stepMap != nil {
66 return 76 return
67 } 77 }
68 78
69 s.stepMap = map[string]*Step{} 79 s.stepMap = map[string]*Step{}
(...skipping 26 matching lines...) Expand all
96 } 106 }
97 } 107 }
98 108
99 s.rootStep.Command = &milo.Command{ 109 s.rootStep.Command = &milo.Command{
100 CommandLine: s.Execution.Command, 110 CommandLine: s.Execution.Command,
101 Cwd: s.Execution.Dir, 111 Cwd: s.Execution.Dir,
102 Environ: env, 112 Environ: env,
103 } 113 }
104 } 114 }
105 115
106 » s.rootStep.Start() 116 » var annotatedNow *google.Timestamp
117 » if !s.Offline {
118 » » annotatedNow = s.now()
119 » }
120 » s.rootStep.Start(annotatedNow)
107 } 121 }
108 122
109 // Append adds an annotation to the state. If the state was updated, Append will 123 // Append adds an annotation to the state. If the state was updated, Append will
110 // return true. 124 // return true.
111 // 125 //
112 // The appended annotation should only contain the annotation text body, not any 126 // The appended annotation should only contain the annotation text body, not any
113 // annotation indicators (e.g., "@@@") that surround it. 127 // annotation indicators (e.g., "@@@") that surround it.
114 // 128 //
115 // If the annotation is invalid or could not be added to the state, an error 129 // If the annotation is invalid or could not be added to the state, an error
116 // will be returned. 130 // will be returned.
117 // 131 //
118 // Steps and descriptions can be found at: 132 // Steps and descriptions can be found at:
119 // https://chromium.googlesource.com/chromium/tools/build/+/master/scripts/ 133 // https://chromium.googlesource.com/chromium/tools/build/+/master/scripts/
120 // master/chromium_step.py 134 // master/chromium_step.py
121 func (s *State) Append(annotation string) error { 135 func (s *State) Append(annotation string) error {
122 s.initialize() 136 s.initialize()
123 137
138 firstAnnotation := !s.startedProcessing
139 s.startedProcessing = true
140
124 command, params := annotation, "" 141 command, params := annotation, ""
125 splitIdx := strings.IndexAny(command, "@ ") 142 splitIdx := strings.IndexAny(command, "@ ")
126 if splitIdx > 0 { 143 if splitIdx > 0 {
127 command, params = command[:splitIdx], command[splitIdx+1:] 144 command, params = command[:splitIdx], command[splitIdx+1:]
128 } 145 }
129 146
130 if s.closed { 147 if s.closed {
131 return nil 148 return nil
132 } 149 }
133 150
134 var updated *Step 151 var updated *Step
135 updatedIf := func(s *Step, b bool) { 152 updatedIf := func(s *Step, b bool) {
136 if b { 153 if b {
137 updated = s 154 updated = s
138 } 155 }
139 } 156 }
140 157
158 annotatedNow := s.currentTimestamp
159 s.currentTimestamp = nil
160 if annotatedNow == nil && !s.Offline {
161 annotatedNow = s.now()
162 }
163
141 switch command { 164 switch command {
165 // @@@CURRENT_TIMESTAMP@unix_timestamp@@@
166 case "CURRENT_TIMESTAMP":
167 // This annotation is printed at the beginning and end of the
168 // stream, as well as before each STEP_STARTED and STEP_CLOSED
169 // annotations. It effectively specifies step start/end times,
170 // including root step.
171 timestamp, err := strconv.ParseFloat(params, 64)
172 if err != nil {
173 return fmt.Errorf("CURRENT_TIMESTAMP parameter %q is not a number: %s", params, err)
174 }
175 s.currentTimestamp = google.NewTimestamp(time.Unix(
176 int64(timestamp),
177 int64(timestamp*1000000000)%1000000000))
178 if firstAnnotation {
179 s.rootStep.StepComponent.Started = s.currentTimestamp
180 }
181
142 // @@@BUILD_STEP <stepname>@@@ 182 // @@@BUILD_STEP <stepname>@@@
143 case "BUILD_STEP": 183 case "BUILD_STEP":
144 // Close the last section. 184 // Close the last section.
145 step := s.CurrentStep() 185 step := s.CurrentStep()
146 if step != nil && step != s.RootStep() { 186 if step != nil && step != s.RootStep() {
147 if step.Name() == params { 187 if step.Name() == params {
148 // Same step; ignore the command. 188 // Same step; ignore the command.
149 break 189 break
150 } 190 }
151 » » » step.Close() 191 » » » step.Close(annotatedNow)
152 } 192 }
153 193
154 step = s.rootStep.AddStep(params) 194 step = s.rootStep.AddStep(params)
155 » » step.Start() 195 » » step.Start(annotatedNow)
156 s.SetCurrentStep(step) 196 s.SetCurrentStep(step)
157 updatedIf(step, true) 197 updatedIf(step, true)
158 198
159 // @@@SEED_STEP <stepname>@@@ 199 // @@@SEED_STEP <stepname>@@@
160 case "SEED_STEP": 200 case "SEED_STEP":
161 step := s.LookupStep(params) 201 step := s.LookupStep(params)
162 if step == nil { 202 if step == nil {
163 step = s.rootStep.AddStep(params) 203 step = s.rootStep.AddStep(params)
164 updatedIf(step, true) 204 updatedIf(step, true)
165 } 205 }
(...skipping 14 matching lines...) Expand all
180 parts := strings.SplitN(params, "@", 2) 220 parts := strings.SplitN(params, "@", 2)
181 if len(parts) != 2 { 221 if len(parts) != 2 {
182 return fmt.Errorf("STEP_LINK link [%s] missing URL", par ts[0]) 222 return fmt.Errorf("STEP_LINK link [%s] missing URL", par ts[0])
183 } 223 }
184 step.AddURLLink(parts[0], parts[1]) 224 step.AddURLLink(parts[0], parts[1])
185 updatedIf(step, true) 225 updatedIf(step, true)
186 226
187 // @@@STEP_STARTED@@@ 227 // @@@STEP_STARTED@@@
188 case "STEP_STARTED": 228 case "STEP_STARTED":
189 step := s.CurrentStep() 229 step := s.CurrentStep()
190 » » updatedIf(step, step.Start()) 230 » » updatedIf(step, step.Start(annotatedNow))
191 231
192 // @@@STEP_WARNINGS@@@ 232 // @@@STEP_WARNINGS@@@
193 case "BUILD_WARNINGS": 233 case "BUILD_WARNINGS":
194 fallthrough 234 fallthrough
195 case "STEP_WARNINGS": 235 case "STEP_WARNINGS":
196 // No warnings because they don't generally help. Builds that wa nt to add 236 // No warnings because they don't generally help. Builds that wa nt to add
197 // information can do so with notes. A "WARNING" state is tradit ionally a 237 // information can do so with notes. A "WARNING" state is tradit ionally a
198 // success state with a call to attention, and that call can be done through 238 // success state with a call to attention, and that call can be done through
199 // other means. 239 // other means.
200 break 240 break
(...skipping 11 matching lines...) Expand all
212 // @@@STEP_EXCEPTION@@@ 252 // @@@STEP_EXCEPTION@@@
213 case "BUILD_EXCEPTION": 253 case "BUILD_EXCEPTION":
214 fallthrough 254 fallthrough
215 case "STEP_EXCEPTION": 255 case "STEP_EXCEPTION":
216 step := s.CurrentStep() 256 step := s.CurrentStep()
217 updatedIf(step, step.SetStatus(milo.Status_EXCEPTION)) 257 updatedIf(step, step.SetStatus(milo.Status_EXCEPTION))
218 258
219 // @@@STEP_CLOSED@@@ 259 // @@@STEP_CLOSED@@@
220 case "STEP_CLOSED": 260 case "STEP_CLOSED":
221 step := s.CurrentStep() 261 step := s.CurrentStep()
222 » » updatedIf(step, step.Close()) 262 » » updatedIf(step, step.Close(annotatedNow))
223 263
224 » » // @@@STEP_LOG_LINE@<label>@<line>@@@ 264 » // @@@STEP_LOG_LINE@<label>@<line>@@@
225 case "STEP_LOG_LINE": 265 case "STEP_LOG_LINE":
226 step := s.CurrentStep() 266 step := s.CurrentStep()
227 267
228 parts := strings.SplitN(params, "@", 2) 268 parts := strings.SplitN(params, "@", 2)
229 label, line := parts[0], "" 269 label, line := parts[0], ""
230 if len(parts) == 2 { 270 if len(parts) == 2 {
231 line = parts[1] 271 line = parts[1]
232 } 272 }
233 updatedIf(step, step.LogLine(label, line)) 273 updatedIf(step, step.LogLine(label, line))
234 274
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after
314 // Finish closes the top-level annotation state and any outstanding steps. 354 // Finish closes the top-level annotation state and any outstanding steps.
315 func (s *State) Finish() { 355 func (s *State) Finish() {
316 s.initialize() 356 s.initialize()
317 s.finishWithStatusImpl(nil) 357 s.finishWithStatusImpl(nil)
318 } 358 }
319 359
320 func (s *State) finishWithStatus(st milo.Status) bool { 360 func (s *State) finishWithStatus(st milo.Status) bool {
321 return s.finishWithStatusImpl(&st) 361 return s.finishWithStatusImpl(&st)
322 } 362 }
323 363
324 func (s *State) finishWithStatusImpl(sp *milo.Status) bool { 364 func (s *State) finishWithStatusImpl(status *milo.Status) bool {
325 if s.closed { 365 if s.closed {
326 return false 366 return false
327 } 367 }
328 368
369 // if s.currentTimestamp is not nil, the last annotation was
370 // CURRENT_TIMESTAMP and s.currentTimestamp contains its value.
371 buildEndTime := s.currentTimestamp
372 s.currentTimestamp = nil
373 if buildEndTime == nil && !s.Offline {
374 buildEndTime = s.now()
375 }
376
329 unfinished := false 377 unfinished := false
330 for _, step := range s.steps[1:] { 378 for _, step := range s.steps[1:] {
331 » » if u := step.closeWithStatus(nil); u { 379 » » if step.closeWithStatus(buildEndTime, nil) {
332 unfinished = true 380 unfinished = true
333 } 381 }
334 } 382 }
335 383
336 // If some steps were unfinished, show a root exception. 384 // If some steps were unfinished, show a root exception.
337 » if unfinished && sp == nil { 385 » if unfinished && status == nil {
338 » » status := milo.Status_EXCEPTION 386 » » exception := milo.Status_EXCEPTION
339 » » sp = &status 387 » » status = &exception
340 } 388 }
341 » s.rootStep.closeWithStatus(sp) 389 » s.rootStep.closeWithStatus(buildEndTime, status)
342 390
343 // Probe the status from our steps, if one is not supplied. 391 // Probe the status from our steps, if one is not supplied.
344 s.closed = true 392 s.closed = true
345 return true 393 return true
346 } 394 }
347 395
348 // LookupStep returns the step with the supplied name, or nil if no such step 396 // LookupStep returns the step with the supplied name, or nil if no such step
349 // exists. 397 // exists.
350 func (s *State) LookupStep(name string) *Step { 398 func (s *State) LookupStep(name string) *Step {
351 return s.stepMap[name] 399 return s.stepMap[name]
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
405 } 453 }
406 454
407 // ForEachStep iterates over all registered steps, invoking the supplied 455 // ForEachStep iterates over all registered steps, invoking the supplied
408 // callback with each step's protobuf state. 456 // callback with each step's protobuf state.
409 func (s *State) ForEachStep(f func(*Step)) { 457 func (s *State) ForEachStep(f func(*Step)) {
410 for _, as := range s.steps { 458 for _, as := range s.steps {
411 f(as) 459 f(as)
412 } 460 }
413 } 461 }
414 462
415 func (s *State) annotationNow() *google.Timestamp { 463 // now returns current time of s.Clock. Defaults to system clock.
464 func (s *State) now() *google.Timestamp {
416 c := s.Clock 465 c := s.Clock
417 if c == nil { 466 if c == nil {
418 c = clock.GetSystemClock() 467 c = clock.GetSystemClock()
419 } 468 }
420 return google.NewTimestamp(c.Now()) 469 return google.NewTimestamp(c.Now())
421 } 470 }
422 471
423 // Step represents a single step. 472 // Step represents a single step.
424 type Step struct { 473 type Step struct {
425 milo.Step 474 milo.Step
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after
523 } 572 }
524 573
525 nas := (&Step{}).initialize(as.s, as, name, index, as.BaseStream(logPath )) 574 nas := (&Step{}).initialize(as.s, as, name, index, as.BaseStream(logPath ))
526 as.substeps = append(as.substeps, nas) 575 as.substeps = append(as.substeps, nas)
527 as.s.registerStep(nas) 576 as.s.registerStep(nas)
528 as.EnsureSubstepLogNameBase(nas.logNameBase) 577 as.EnsureSubstepLogNameBase(nas.logNameBase)
529 return nas 578 return nas
530 } 579 }
531 580
532 // Start marks the Step as started. 581 // Start marks the Step as started.
533 func (as *Step) Start() bool { 582 func (as *Step) Start(startTime *google.Timestamp) bool {
534 if as.StepComponent.Started != nil { 583 if as.StepComponent.Started != nil {
535 return false 584 return false
536 } 585 }
537 » as.StepComponent.Started = as.s.annotationNow() 586 » as.StepComponent.Started = startTime
538 return true 587 return true
539 } 588 }
540 589
541 // Close closes this step and any outstanding resources that it owns. 590 // Close closes this step and any outstanding resources that it owns.
542 func (as *Step) Close() bool { 591 // If it is already closed, does not have side effects and returns false.
543 » return as.closeWithStatus(nil) 592 func (as *Step) Close(closeTime *google.Timestamp) bool {
593 » return as.closeWithStatus(closeTime, nil)
544 } 594 }
545 595
546 func (as *Step) closeWithStatus(sp *milo.Status) bool { 596 func (as *Step) closeWithStatus(closeTime *google.Timestamp, sp *milo.Status) bo ol {
547 if as.closed { 597 if as.closed {
548 return false 598 return false
549 } 599 }
550 600
551 // Close our outstanding substeps, and get their highest status value. 601 // Close our outstanding substeps, and get their highest status value.
552 stepStatus := milo.Status_SUCCESS 602 stepStatus := milo.Status_SUCCESS
553 for _, sub := range as.substeps { 603 for _, sub := range as.substeps {
554 » » sub.Close() 604 » » sub.Close(closeTime)
555 if sub.StepComponent.Status > stepStatus { 605 if sub.StepComponent.Status > stepStatus {
556 stepStatus = sub.StepComponent.Status 606 stepStatus = sub.StepComponent.Status
557 } 607 }
558 } 608 }
559 609
560 // Close any ourstanding log streams. 610 // Close any ourstanding log streams.
561 for l := range as.logLines { 611 for l := range as.logLines {
562 as.LogEnd(l) 612 as.LogEnd(l)
563 } 613 }
564 614
565 // If a status is provided, use it. 615 // If a status is provided, use it.
566 if sp != nil { 616 if sp != nil {
567 stepStatus = *sp 617 stepStatus = *sp
568 } 618 }
569 619
570 if as.StepComponent.Status == milo.Status_RUNNING { 620 if as.StepComponent.Status == milo.Status_RUNNING {
571 as.StepComponent.Status = stepStatus 621 as.StepComponent.Status = stepStatus
572 } 622 }
573 » as.StepComponent.Ended = as.s.annotationNow() 623 » as.StepComponent.Ended = closeTime
574 if as.StepComponent.Started == nil { 624 if as.StepComponent.Started == nil {
575 as.StepComponent.Started = as.StepComponent.Ended 625 as.StepComponent.Started = as.StepComponent.Ended
576 } 626 }
577 as.closed = true 627 as.closed = true
578 as.s.unregisterStep(as) 628 as.s.unregisterStep(as)
579 as.s.Callbacks.Updated(as) 629 as.s.Callbacks.Updated(as)
580 as.s.Callbacks.StepClosed(as) 630 as.s.Callbacks.StepClosed(as)
581 return true 631 return true
582 } 632 }
583 633
(...skipping 142 matching lines...) Expand 10 before | Expand all | Expand 10 after
726 func (as *Step) EnsureSubstepLogNameBase(name types.StreamName) bool { 776 func (as *Step) EnsureSubstepLogNameBase(name types.StreamName) bool {
727 nameStr := string(name) 777 nameStr := string(name)
728 for _, lnb := range as.SubstepLogdogNameBase { 778 for _, lnb := range as.SubstepLogdogNameBase {
729 if lnb == nameStr { 779 if lnb == nameStr {
730 return false 780 return false
731 } 781 }
732 } 782 }
733 as.SubstepLogdogNameBase = append(as.SubstepLogdogNameBase, nameStr) 783 as.SubstepLogdogNameBase = append(as.SubstepLogdogNameBase, nameStr)
734 return true 784 return true
735 } 785 }
OLDNEW
« no previous file with comments | « appengine/cmd/milo/swarming/testdata/build-timeout ('k') | client/logdog/annotee/annotation/annotation_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698