| OLD | NEW |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 } |
| OLD | NEW |