github.com/bir3/gocompiler@v0.9.2202/src/internal/trace/traceviewer/emitter.go (about) 1 // Copyright 2023 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 traceviewer 6 7 import ( 8 "encoding/json" 9 "fmt" 10 "github.com/bir3/gocompiler/src/internal/trace" 11 "github.com/bir3/gocompiler/src/internal/trace/traceviewer/format" 12 "io" 13 "strconv" 14 "time" 15 ) 16 17 type TraceConsumer struct { 18 ConsumeTimeUnit func(unit string) 19 ConsumeViewerEvent func(v *format.Event, required bool) 20 ConsumeViewerFrame func(key string, f format.Frame) 21 Flush func() 22 } 23 24 // ViewerDataTraceConsumer returns a TraceConsumer that writes to w. The 25 // startIdx and endIdx are used for splitting large traces. They refer to 26 // indexes in the traceEvents output array, not the events in the trace input. 27 func ViewerDataTraceConsumer(w io.Writer, startIdx, endIdx int64) TraceConsumer { 28 allFrames := make(map[string]format.Frame) 29 requiredFrames := make(map[string]format.Frame) 30 enc := json.NewEncoder(w) 31 written := 0 32 index := int64(-1) 33 34 io.WriteString(w, "{") 35 return TraceConsumer{ 36 ConsumeTimeUnit: func(unit string) { 37 io.WriteString(w, `"displayTimeUnit":`) 38 enc.Encode(unit) 39 io.WriteString(w, ",") 40 }, 41 ConsumeViewerEvent: func(v *format.Event, required bool) { 42 index++ 43 if !required && (index < startIdx || index > endIdx) { 44 // not in the range. Skip! 45 return 46 } 47 WalkStackFrames(allFrames, v.Stack, func(id int) { 48 s := strconv.Itoa(id) 49 requiredFrames[s] = allFrames[s] 50 }) 51 WalkStackFrames(allFrames, v.EndStack, func(id int) { 52 s := strconv.Itoa(id) 53 requiredFrames[s] = allFrames[s] 54 }) 55 if written == 0 { 56 io.WriteString(w, `"traceEvents": [`) 57 } 58 if written > 0 { 59 io.WriteString(w, ",") 60 } 61 enc.Encode(v) 62 // TODO(mknyszek): get rid of the extra \n inserted by enc.Encode. 63 // Same should be applied to splittingTraceConsumer. 64 written++ 65 }, 66 ConsumeViewerFrame: func(k string, v format.Frame) { 67 allFrames[k] = v 68 }, 69 Flush: func() { 70 io.WriteString(w, `], "stackFrames":`) 71 enc.Encode(requiredFrames) 72 io.WriteString(w, `}`) 73 }, 74 } 75 } 76 77 func SplittingTraceConsumer(max int) (*splitter, TraceConsumer) { 78 type eventSz struct { 79 Time float64 80 Sz int 81 Frames []int 82 } 83 84 var ( 85 // data.Frames contains only the frames for required events. 86 data = format.Data{Frames: make(map[string]format.Frame)} 87 88 allFrames = make(map[string]format.Frame) 89 90 sizes []eventSz 91 cw countingWriter 92 ) 93 94 s := new(splitter) 95 96 return s, TraceConsumer{ 97 ConsumeTimeUnit: func(unit string) { 98 data.TimeUnit = unit 99 }, 100 ConsumeViewerEvent: func(v *format.Event, required bool) { 101 if required { 102 // Store required events inside data so flush 103 // can include them in the required part of the 104 // trace. 105 data.Events = append(data.Events, v) 106 WalkStackFrames(allFrames, v.Stack, func(id int) { 107 s := strconv.Itoa(id) 108 data.Frames[s] = allFrames[s] 109 }) 110 WalkStackFrames(allFrames, v.EndStack, func(id int) { 111 s := strconv.Itoa(id) 112 data.Frames[s] = allFrames[s] 113 }) 114 return 115 } 116 enc := json.NewEncoder(&cw) 117 enc.Encode(v) 118 size := eventSz{Time: v.Time, Sz: cw.size + 1} // +1 for ",". 119 // Add referenced stack frames. Their size is computed 120 // in flush, where we can dedup across events. 121 WalkStackFrames(allFrames, v.Stack, func(id int) { 122 size.Frames = append(size.Frames, id) 123 }) 124 WalkStackFrames(allFrames, v.EndStack, func(id int) { 125 size.Frames = append(size.Frames, id) // This may add duplicates. We'll dedup later. 126 }) 127 sizes = append(sizes, size) 128 cw.size = 0 129 }, 130 ConsumeViewerFrame: func(k string, v format.Frame) { 131 allFrames[k] = v 132 }, 133 Flush: func() { 134 // Calculate size of the mandatory part of the trace. 135 // This includes thread names and stack frames for 136 // required events. 137 cw.size = 0 138 enc := json.NewEncoder(&cw) 139 enc.Encode(data) 140 requiredSize := cw.size 141 142 // Then calculate size of each individual event and 143 // their stack frames, grouping them into ranges. We 144 // only include stack frames relevant to the events in 145 // the range to reduce overhead. 146 147 var ( 148 start = 0 149 150 eventsSize = 0 151 152 frames = make(map[string]format.Frame) 153 framesSize = 0 154 ) 155 for i, ev := range sizes { 156 eventsSize += ev.Sz 157 158 // Add required stack frames. Note that they 159 // may already be in the map. 160 for _, id := range ev.Frames { 161 s := strconv.Itoa(id) 162 _, ok := frames[s] 163 if ok { 164 continue 165 } 166 f := allFrames[s] 167 frames[s] = f 168 framesSize += stackFrameEncodedSize(uint(id), f) 169 } 170 171 total := requiredSize + framesSize + eventsSize 172 if total < max { 173 continue 174 } 175 176 // Reached max size, commit this range and 177 // start a new range. 178 startTime := time.Duration(sizes[start].Time * 1000) 179 endTime := time.Duration(ev.Time * 1000) 180 s.Ranges = append(s.Ranges, Range{ 181 Name: fmt.Sprintf("%v-%v", startTime, endTime), 182 Start: start, 183 End: i + 1, 184 StartTime: int64(startTime), 185 EndTime: int64(endTime), 186 }) 187 start = i + 1 188 frames = make(map[string]format.Frame) 189 framesSize = 0 190 eventsSize = 0 191 } 192 if len(s.Ranges) <= 1 { 193 s.Ranges = nil 194 return 195 } 196 197 if end := len(sizes) - 1; start < end { 198 s.Ranges = append(s.Ranges, Range{ 199 Name: fmt.Sprintf("%v-%v", time.Duration(sizes[start].Time*1000), time.Duration(sizes[end].Time*1000)), 200 Start: start, 201 End: end, 202 StartTime: int64(sizes[start].Time * 1000), 203 EndTime: int64(sizes[end].Time * 1000), 204 }) 205 } 206 }, 207 } 208 } 209 210 type splitter struct { 211 Ranges []Range 212 } 213 214 type countingWriter struct { 215 size int 216 } 217 218 func (cw *countingWriter) Write(data []byte) (int, error) { 219 cw.size += len(data) 220 return len(data), nil 221 } 222 223 func stackFrameEncodedSize(id uint, f format.Frame) int { 224 // We want to know the marginal size of traceviewer.Data.Frames for 225 // each event. Running full JSON encoding of the map for each event is 226 // far too slow. 227 // 228 // Since the format is fixed, we can easily compute the size without 229 // encoding. 230 // 231 // A single entry looks like one of the following: 232 // 233 // "1":{"name":"main.main:30"}, 234 // "10":{"name":"pkg.NewSession:173","parent":9}, 235 // 236 // The parent is omitted if 0. The trailing comma is omitted from the 237 // last entry, but we don't need that much precision. 238 const ( 239 baseSize = len(`"`) + len(`":{"name":"`) + len(`"},`) 240 241 // Don't count the trailing quote on the name, as that is 242 // counted in baseSize. 243 parentBaseSize = len(`,"parent":`) 244 ) 245 246 size := baseSize 247 248 size += len(f.Name) 249 250 // Bytes for id (always positive). 251 for id > 0 { 252 size += 1 253 id /= 10 254 } 255 256 if f.Parent > 0 { 257 size += parentBaseSize 258 // Bytes for parent (always positive). 259 for f.Parent > 0 { 260 size += 1 261 f.Parent /= 10 262 } 263 } 264 265 return size 266 } 267 268 // WalkStackFrames calls fn for id and all of its parent frames from allFrames. 269 func WalkStackFrames(allFrames map[string]format.Frame, id int, fn func(id int)) { 270 for id != 0 { 271 f, ok := allFrames[strconv.Itoa(id)] 272 if !ok { 273 break 274 } 275 fn(id) 276 id = f.Parent 277 } 278 } 279 280 type Mode int 281 282 const ( 283 ModeGoroutineOriented Mode = 1 << iota 284 ModeTaskOriented 285 ModeThreadOriented // Mutually exclusive with ModeGoroutineOriented. 286 ) 287 288 // NewEmitter returns a new Emitter that writes to c. The rangeStart and 289 // rangeEnd args are used for splitting large traces. 290 func NewEmitter(c TraceConsumer, rangeStart, rangeEnd time.Duration) *Emitter { 291 c.ConsumeTimeUnit("ns") 292 293 return &Emitter{ 294 c: c, 295 rangeStart: rangeStart, 296 rangeEnd: rangeEnd, 297 frameTree: frameNode{children: make(map[uint64]frameNode)}, 298 resources: make(map[uint64]string), 299 tasks: make(map[uint64]task), 300 } 301 } 302 303 type Emitter struct { 304 c TraceConsumer 305 rangeStart time.Duration 306 rangeEnd time.Duration 307 308 heapStats, prevHeapStats heapStats 309 gstates, prevGstates [gStateCount]int64 310 threadStats, prevThreadStats [threadStateCount]int64 311 gomaxprocs uint64 312 frameTree frameNode 313 frameSeq int 314 arrowSeq uint64 315 filter func(uint64) bool 316 resourceType string 317 resources map[uint64]string 318 focusResource uint64 319 tasks map[uint64]task 320 asyncSliceSeq uint64 321 } 322 323 type task struct { 324 name string 325 sortIndex int 326 } 327 328 func (e *Emitter) Gomaxprocs(v uint64) { 329 if v > e.gomaxprocs { 330 e.gomaxprocs = v 331 } 332 } 333 334 func (e *Emitter) Resource(id uint64, name string) { 335 if e.filter != nil && !e.filter(id) { 336 return 337 } 338 e.resources[id] = name 339 } 340 341 func (e *Emitter) SetResourceType(name string) { 342 e.resourceType = name 343 } 344 345 func (e *Emitter) SetResourceFilter(filter func(uint64) bool) { 346 e.filter = filter 347 } 348 349 func (e *Emitter) Task(id uint64, name string, sortIndex int) { 350 e.tasks[id] = task{name, sortIndex} 351 } 352 353 func (e *Emitter) Slice(s SliceEvent) { 354 if e.filter != nil && !e.filter(s.Resource) { 355 return 356 } 357 e.slice(s, format.ProcsSection, "") 358 } 359 360 func (e *Emitter) TaskSlice(s SliceEvent) { 361 e.slice(s, format.TasksSection, pickTaskColor(s.Resource)) 362 } 363 364 func (e *Emitter) slice(s SliceEvent, sectionID uint64, cname string) { 365 if !e.tsWithinRange(s.Ts) && !e.tsWithinRange(s.Ts+s.Dur) { 366 return 367 } 368 e.OptionalEvent(&format.Event{ 369 Name: s.Name, 370 Phase: "X", 371 Time: viewerTime(s.Ts), 372 Dur: viewerTime(s.Dur), 373 PID: sectionID, 374 TID: s.Resource, 375 Stack: s.Stack, 376 EndStack: s.EndStack, 377 Arg: s.Arg, 378 Cname: cname, 379 }) 380 } 381 382 type SliceEvent struct { 383 Name string 384 Ts time.Duration 385 Dur time.Duration 386 Resource uint64 387 Stack int 388 EndStack int 389 Arg any 390 } 391 392 func (e *Emitter) AsyncSlice(s AsyncSliceEvent) { 393 if !e.tsWithinRange(s.Ts) && !e.tsWithinRange(s.Ts+s.Dur) { 394 return 395 } 396 if e.filter != nil && !e.filter(s.Resource) { 397 return 398 } 399 cname := "" 400 if s.TaskColorIndex != 0 { 401 cname = pickTaskColor(s.TaskColorIndex) 402 } 403 e.asyncSliceSeq++ 404 e.OptionalEvent(&format.Event{ 405 Category: s.Category, 406 Name: s.Name, 407 Phase: "b", 408 Time: viewerTime(s.Ts), 409 TID: s.Resource, 410 ID: e.asyncSliceSeq, 411 Scope: s.Scope, 412 Stack: s.Stack, 413 Cname: cname, 414 }) 415 e.OptionalEvent(&format.Event{ 416 Category: s.Category, 417 Name: s.Name, 418 Phase: "e", 419 Time: viewerTime(s.Ts + s.Dur), 420 TID: s.Resource, 421 ID: e.asyncSliceSeq, 422 Scope: s.Scope, 423 Stack: s.EndStack, 424 Arg: s.Arg, 425 Cname: cname, 426 }) 427 } 428 429 type AsyncSliceEvent struct { 430 SliceEvent 431 Category string 432 Scope string 433 TaskColorIndex uint64 // Take on the same color as the task with this ID. 434 } 435 436 func (e *Emitter) Instant(i InstantEvent) { 437 if !e.tsWithinRange(i.Ts) { 438 return 439 } 440 if e.filter != nil && !e.filter(i.Resource) { 441 return 442 } 443 cname := "" 444 e.OptionalEvent(&format.Event{ 445 Name: i.Name, 446 Category: i.Category, 447 Phase: "I", 448 Scope: "t", 449 Time: viewerTime(i.Ts), 450 PID: format.ProcsSection, 451 TID: i.Resource, 452 Stack: i.Stack, 453 Cname: cname, 454 Arg: i.Arg, 455 }) 456 } 457 458 type InstantEvent struct { 459 Ts time.Duration 460 Name string 461 Category string 462 Resource uint64 463 Stack int 464 Arg any 465 } 466 467 func (e *Emitter) Arrow(a ArrowEvent) { 468 if e.filter != nil && (!e.filter(a.FromResource) || !e.filter(a.ToResource)) { 469 return 470 } 471 e.arrow(a, format.ProcsSection) 472 } 473 474 func (e *Emitter) TaskArrow(a ArrowEvent) { 475 e.arrow(a, format.TasksSection) 476 } 477 478 func (e *Emitter) arrow(a ArrowEvent, sectionID uint64) { 479 if !e.tsWithinRange(a.Start) || !e.tsWithinRange(a.End) { 480 return 481 } 482 e.arrowSeq++ 483 e.OptionalEvent(&format.Event{ 484 Name: a.Name, 485 Phase: "s", 486 TID: a.FromResource, 487 PID: sectionID, 488 ID: e.arrowSeq, 489 Time: viewerTime(a.Start), 490 Stack: a.FromStack, 491 }) 492 e.OptionalEvent(&format.Event{ 493 Name: a.Name, 494 Phase: "t", 495 TID: a.ToResource, 496 PID: sectionID, 497 ID: e.arrowSeq, 498 Time: viewerTime(a.End), 499 }) 500 } 501 502 type ArrowEvent struct { 503 Name string 504 Start time.Duration 505 End time.Duration 506 FromResource uint64 507 FromStack int 508 ToResource uint64 509 } 510 511 func (e *Emitter) Event(ev *format.Event) { 512 e.c.ConsumeViewerEvent(ev, true) 513 } 514 515 func (e *Emitter) HeapAlloc(ts time.Duration, v uint64) { 516 e.heapStats.heapAlloc = v 517 e.emitHeapCounters(ts) 518 } 519 520 func (e *Emitter) Focus(id uint64) { 521 e.focusResource = id 522 } 523 524 func (e *Emitter) GoroutineTransition(ts time.Duration, from, to GState) { 525 e.gstates[from]-- 526 e.gstates[to]++ 527 if e.prevGstates == e.gstates { 528 return 529 } 530 if e.tsWithinRange(ts) { 531 e.OptionalEvent(&format.Event{ 532 Name: "Goroutines", 533 Phase: "C", 534 Time: viewerTime(ts), 535 PID: 1, 536 Arg: &format.GoroutineCountersArg{ 537 Running: uint64(e.gstates[GRunning]), 538 Runnable: uint64(e.gstates[GRunnable]), 539 GCWaiting: uint64(e.gstates[GWaitingGC]), 540 }, 541 }) 542 } 543 e.prevGstates = e.gstates 544 } 545 546 func (e *Emitter) IncThreadStateCount(ts time.Duration, state ThreadState, delta int64) { 547 e.threadStats[state] += delta 548 if e.prevThreadStats == e.threadStats { 549 return 550 } 551 if e.tsWithinRange(ts) { 552 e.OptionalEvent(&format.Event{ 553 Name: "Threads", 554 Phase: "C", 555 Time: viewerTime(ts), 556 PID: 1, 557 Arg: &format.ThreadCountersArg{ 558 Running: int64(e.threadStats[ThreadStateRunning]), 559 InSyscall: int64(e.threadStats[ThreadStateInSyscall]), 560 // TODO(mknyszek): Why is InSyscallRuntime not included here? 561 }, 562 }) 563 } 564 e.prevThreadStats = e.threadStats 565 } 566 567 func (e *Emitter) HeapGoal(ts time.Duration, v uint64) { 568 // This cutoff at 1 PiB is a Workaround for https://github.com/golang/go/issues/63864. 569 // 570 // TODO(mknyszek): Remove this once the problem has been fixed. 571 const PB = 1 << 50 572 if v > PB { 573 v = 0 574 } 575 e.heapStats.nextGC = v 576 e.emitHeapCounters(ts) 577 } 578 579 func (e *Emitter) emitHeapCounters(ts time.Duration) { 580 if e.prevHeapStats == e.heapStats { 581 return 582 } 583 diff := uint64(0) 584 if e.heapStats.nextGC > e.heapStats.heapAlloc { 585 diff = e.heapStats.nextGC - e.heapStats.heapAlloc 586 } 587 if e.tsWithinRange(ts) { 588 e.OptionalEvent(&format.Event{ 589 Name: "Heap", 590 Phase: "C", 591 Time: viewerTime(ts), 592 PID: 1, 593 Arg: &format.HeapCountersArg{Allocated: e.heapStats.heapAlloc, NextGC: diff}, 594 }) 595 } 596 e.prevHeapStats = e.heapStats 597 } 598 599 // Err returns an error if the emitter is in an invalid state. 600 func (e *Emitter) Err() error { 601 if e.gstates[GRunnable] < 0 || e.gstates[GRunning] < 0 || e.threadStats[ThreadStateInSyscall] < 0 || e.threadStats[ThreadStateInSyscallRuntime] < 0 { 602 return fmt.Errorf( 603 "runnable=%d running=%d insyscall=%d insyscallRuntime=%d", 604 e.gstates[GRunnable], 605 e.gstates[GRunning], 606 e.threadStats[ThreadStateInSyscall], 607 e.threadStats[ThreadStateInSyscallRuntime], 608 ) 609 } 610 return nil 611 } 612 613 func (e *Emitter) tsWithinRange(ts time.Duration) bool { 614 return e.rangeStart <= ts && ts <= e.rangeEnd 615 } 616 617 // OptionalEvent emits ev if it's within the time range of of the consumer, i.e. 618 // the selected trace split range. 619 func (e *Emitter) OptionalEvent(ev *format.Event) { 620 e.c.ConsumeViewerEvent(ev, false) 621 } 622 623 func (e *Emitter) Flush() { 624 e.processMeta(format.StatsSection, "STATS", 0) 625 626 if len(e.tasks) != 0 { 627 e.processMeta(format.TasksSection, "TASKS", 1) 628 } 629 for id, task := range e.tasks { 630 e.threadMeta(format.TasksSection, id, task.name, task.sortIndex) 631 } 632 633 e.processMeta(format.ProcsSection, e.resourceType, 2) 634 635 e.threadMeta(format.ProcsSection, trace.GCP, "GC", -6) 636 e.threadMeta(format.ProcsSection, trace.NetpollP, "Network", -5) 637 e.threadMeta(format.ProcsSection, trace.TimerP, "Timers", -4) 638 e.threadMeta(format.ProcsSection, trace.SyscallP, "Syscalls", -3) 639 640 for id, name := range e.resources { 641 priority := int(id) 642 if e.focusResource != 0 && id == e.focusResource { 643 // Put the focus goroutine on top. 644 priority = -2 645 } 646 e.threadMeta(format.ProcsSection, id, name, priority) 647 } 648 649 e.c.Flush() 650 } 651 652 func (e *Emitter) threadMeta(sectionID, tid uint64, name string, priority int) { 653 e.Event(&format.Event{ 654 Name: "thread_name", 655 Phase: "M", 656 PID: sectionID, 657 TID: tid, 658 Arg: &format.NameArg{Name: name}, 659 }) 660 e.Event(&format.Event{ 661 Name: "thread_sort_index", 662 Phase: "M", 663 PID: sectionID, 664 TID: tid, 665 Arg: &format.SortIndexArg{Index: priority}, 666 }) 667 } 668 669 func (e *Emitter) processMeta(sectionID uint64, name string, priority int) { 670 e.Event(&format.Event{ 671 Name: "process_name", 672 Phase: "M", 673 PID: sectionID, 674 Arg: &format.NameArg{Name: name}, 675 }) 676 e.Event(&format.Event{ 677 Name: "process_sort_index", 678 Phase: "M", 679 PID: sectionID, 680 Arg: &format.SortIndexArg{Index: priority}, 681 }) 682 } 683 684 // Stack emits the given frames and returns a unique id for the stack. No 685 // pointers to the given data are being retained beyond the call to Stack. 686 func (e *Emitter) Stack(stk []*trace.Frame) int { 687 return e.buildBranch(e.frameTree, stk) 688 } 689 690 // buildBranch builds one branch in the prefix tree rooted at ctx.frameTree. 691 func (e *Emitter) buildBranch(parent frameNode, stk []*trace.Frame) int { 692 if len(stk) == 0 { 693 return parent.id 694 } 695 last := len(stk) - 1 696 frame := stk[last] 697 stk = stk[:last] 698 699 node, ok := parent.children[frame.PC] 700 if !ok { 701 e.frameSeq++ 702 node.id = e.frameSeq 703 node.children = make(map[uint64]frameNode) 704 parent.children[frame.PC] = node 705 e.c.ConsumeViewerFrame(strconv.Itoa(node.id), format.Frame{Name: fmt.Sprintf("%v:%v", frame.Fn, frame.Line), Parent: parent.id}) 706 } 707 return e.buildBranch(node, stk) 708 } 709 710 type heapStats struct { 711 heapAlloc uint64 712 nextGC uint64 713 } 714 715 func viewerTime(t time.Duration) float64 { 716 return float64(t) / float64(time.Microsecond) 717 } 718 719 type GState int 720 721 const ( 722 GDead GState = iota 723 GRunnable 724 GRunning 725 GWaiting 726 GWaitingGC 727 728 gStateCount 729 ) 730 731 type ThreadState int 732 733 const ( 734 ThreadStateInSyscall ThreadState = iota 735 ThreadStateInSyscallRuntime 736 ThreadStateRunning 737 738 threadStateCount 739 ) 740 741 type frameNode struct { 742 id int 743 children map[uint64]frameNode 744 } 745 746 // Mapping from more reasonable color names to the reserved color names in 747 // https://github.com/catapult-project/catapult/blob/master/tracing/tracing/base/color_scheme.html#L50 748 // The chrome trace viewer allows only those as cname values. 749 const ( 750 colorLightMauve = "thread_state_uninterruptible" // 182, 125, 143 751 colorOrange = "thread_state_iowait" // 255, 140, 0 752 colorSeafoamGreen = "thread_state_running" // 126, 200, 148 753 colorVistaBlue = "thread_state_runnable" // 133, 160, 210 754 colorTan = "thread_state_unknown" // 199, 155, 125 755 colorIrisBlue = "background_memory_dump" // 0, 180, 180 756 colorMidnightBlue = "light_memory_dump" // 0, 0, 180 757 colorDeepMagenta = "detailed_memory_dump" // 180, 0, 180 758 colorBlue = "vsync_highlight_color" // 0, 0, 255 759 colorGrey = "generic_work" // 125, 125, 125 760 colorGreen = "good" // 0, 125, 0 761 colorDarkGoldenrod = "bad" // 180, 125, 0 762 colorPeach = "terrible" // 180, 0, 0 763 colorBlack = "black" // 0, 0, 0 764 colorLightGrey = "grey" // 221, 221, 221 765 colorWhite = "white" // 255, 255, 255 766 colorYellow = "yellow" // 255, 255, 0 767 colorOlive = "olive" // 100, 100, 0 768 colorCornflowerBlue = "rail_response" // 67, 135, 253 769 colorSunsetOrange = "rail_animation" // 244, 74, 63 770 colorTangerine = "rail_idle" // 238, 142, 0 771 colorShamrockGreen = "rail_load" // 13, 168, 97 772 colorGreenishYellow = "startup" // 230, 230, 0 773 colorDarkGrey = "heap_dump_stack_frame" // 128, 128, 128 774 colorTawny = "heap_dump_child_node_arrow" // 204, 102, 0 775 colorLemon = "cq_build_running" // 255, 255, 119 776 colorLime = "cq_build_passed" // 153, 238, 102 777 colorPink = "cq_build_failed" // 238, 136, 136 778 colorSilver = "cq_build_abandoned" // 187, 187, 187 779 colorManzGreen = "cq_build_attempt_runnig" // 222, 222, 75 780 colorKellyGreen = "cq_build_attempt_passed" // 108, 218, 35 781 colorAnotherGrey = "cq_build_attempt_failed" // 187, 187, 187 782 ) 783 784 var colorForTask = []string{ 785 colorLightMauve, 786 colorOrange, 787 colorSeafoamGreen, 788 colorVistaBlue, 789 colorTan, 790 colorMidnightBlue, 791 colorIrisBlue, 792 colorDeepMagenta, 793 colorGreen, 794 colorDarkGoldenrod, 795 colorPeach, 796 colorOlive, 797 colorCornflowerBlue, 798 colorSunsetOrange, 799 colorTangerine, 800 colorShamrockGreen, 801 colorTawny, 802 colorLemon, 803 colorLime, 804 colorPink, 805 colorSilver, 806 colorManzGreen, 807 colorKellyGreen, 808 } 809 810 func pickTaskColor(id uint64) string { 811 idx := id % uint64(len(colorForTask)) 812 return colorForTask[idx] 813 }