// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package traceviewer

import (
	
	
	
	
	
	
	
)

type TraceConsumer struct {
	ConsumeTimeUnit    func(unit string)
	ConsumeViewerEvent func(v *format.Event, required bool)
	ConsumeViewerFrame func(key string, f format.Frame)
	Flush              func()
}

// ViewerDataTraceConsumer returns a TraceConsumer that writes to w. The
// startIdx and endIdx are used for splitting large traces. They refer to
// indexes in the traceEvents output array, not the events in the trace input.
func ( io.Writer, ,  int64) TraceConsumer {
	 := make(map[string]format.Frame)
	 := make(map[string]format.Frame)
	 := json.NewEncoder()
	 := 0
	 := int64(-1)

	io.WriteString(, "{")
	return TraceConsumer{
		ConsumeTimeUnit: func( string) {
			io.WriteString(, `"displayTimeUnit":`)
			.Encode()
			io.WriteString(, ",")
		},
		ConsumeViewerEvent: func( *format.Event,  bool) {
			++
			if ! && ( <  ||  > ) {
				// not in the range. Skip!
				return
			}
			WalkStackFrames(, .Stack, func( int) {
				 := strconv.Itoa()
				[] = []
			})
			WalkStackFrames(, .EndStack, func( int) {
				 := strconv.Itoa()
				[] = []
			})
			if  == 0 {
				io.WriteString(, `"traceEvents": [`)
			}
			if  > 0 {
				io.WriteString(, ",")
			}
			.Encode()
			// TODO(mknyszek): get rid of the extra \n inserted by enc.Encode.
			// Same should be applied to splittingTraceConsumer.
			++
		},
		ConsumeViewerFrame: func( string,  format.Frame) {
			[] = 
		},
		Flush: func() {
			io.WriteString(, `], "stackFrames":`)
			.Encode()
			io.WriteString(, `}`)
		},
	}
}

func ( int) (*splitter, TraceConsumer) {
	type  struct {
		   float64
		     int
		 []int
	}

	var (
		// data.Frames contains only the frames for required events.
		 = format.Data{Frames: make(map[string]format.Frame)}

		 = make(map[string]format.Frame)

		 []
		    countingWriter
	)

	 := new(splitter)

	return , TraceConsumer{
		ConsumeTimeUnit: func( string) {
			.TimeUnit = 
		},
		ConsumeViewerEvent: func( *format.Event,  bool) {
			if  {
				// Store required events inside data so flush
				// can include them in the required part of the
				// trace.
				.Events = append(.Events, )
				WalkStackFrames(, .Stack, func( int) {
					 := strconv.Itoa()
					.Frames[] = []
				})
				WalkStackFrames(, .EndStack, func( int) {
					 := strconv.Itoa()
					.Frames[] = []
				})
				return
			}
			 := json.NewEncoder(&)
			.Encode()
			 := {: .Time, : .size + 1} // +1 for ",".
			// Add referenced stack frames. Their size is computed
			// in flush, where we can dedup across events.
			WalkStackFrames(, .Stack, func( int) {
				. = append(., )
			})
			WalkStackFrames(, .EndStack, func( int) {
				. = append(., ) // This may add duplicates. We'll dedup later.
			})
			 = append(, )
			.size = 0
		},
		ConsumeViewerFrame: func( string,  format.Frame) {
			[] = 
		},
		Flush: func() {
			// Calculate size of the mandatory part of the trace.
			// This includes thread names and stack frames for
			// required events.
			.size = 0
			 := json.NewEncoder(&)
			.Encode()
			 := .size

			// Then calculate size of each individual event and
			// their stack frames, grouping them into ranges. We
			// only include stack frames relevant to the events in
			// the range to reduce overhead.

			var (
				 = 0

				 = 0

				     = make(map[string]format.Frame)
				 = 0
			)
			for ,  := range  {
				 += .

				// Add required stack frames. Note that they
				// may already be in the map.
				for ,  := range . {
					 := strconv.Itoa()
					,  := []
					if  {
						continue
					}
					 := []
					[] = 
					 += stackFrameEncodedSize(uint(), )
				}

				 :=  +  + 
				if  <  {
					continue
				}

				// Reached max size, commit this range and
				// start a new range.
				 := time.Duration([]. * 1000)
				 := time.Duration(. * 1000)
				.Ranges = append(.Ranges, Range{
					Name:      fmt.Sprintf("%v-%v", , ),
					Start:     ,
					End:        + 1,
					StartTime: int64(),
					EndTime:   int64(),
				})
				 =  + 1
				 = make(map[string]format.Frame)
				 = 0
				 = 0
			}
			if len(.Ranges) <= 1 {
				.Ranges = nil
				return
			}

			if  := len() - 1;  <  {
				.Ranges = append(.Ranges, Range{
					Name:      fmt.Sprintf("%v-%v", time.Duration([].*1000), time.Duration([].*1000)),
					Start:     ,
					End:       ,
					StartTime: int64([]. * 1000),
					EndTime:   int64([]. * 1000),
				})
			}
		},
	}
}

type splitter struct {
	Ranges []Range
}

type countingWriter struct {
	size int
}

func ( *countingWriter) ( []byte) (int, error) {
	.size += len()
	return len(), nil
}

func stackFrameEncodedSize( uint,  format.Frame) int {
	// We want to know the marginal size of traceviewer.Data.Frames for
	// each event. Running full JSON encoding of the map for each event is
	// far too slow.
	//
	// Since the format is fixed, we can easily compute the size without
	// encoding.
	//
	// A single entry looks like one of the following:
	//
	//   "1":{"name":"main.main:30"},
	//   "10":{"name":"pkg.NewSession:173","parent":9},
	//
	// The parent is omitted if 0. The trailing comma is omitted from the
	// last entry, but we don't need that much precision.
	const (
		 = len(`"`) + len(`":{"name":"`) + len(`"},`)

		// Don't count the trailing quote on the name, as that is
		// counted in baseSize.
		 = len(`,"parent":`)
	)

	 := 

	 += len(.Name)

	// Bytes for id (always positive).
	for  > 0 {
		 += 1
		 /= 10
	}

	if .Parent > 0 {
		 += 
		// Bytes for parent (always positive).
		for .Parent > 0 {
			 += 1
			.Parent /= 10
		}
	}

	return 
}

// WalkStackFrames calls fn for id and all of its parent frames from allFrames.
func ( map[string]format.Frame,  int,  func( int)) {
	for  != 0 {
		,  := [strconv.Itoa()]
		if ! {
			break
		}
		()
		 = .Parent
	}
}

type Mode int

const (
	ModeGoroutineOriented Mode = 1 << iota
	ModeTaskOriented
	ModeThreadOriented // Mutually exclusive with ModeGoroutineOriented.
)

// NewEmitter returns a new Emitter that writes to c. The rangeStart and
// rangeEnd args are used for splitting large traces.
func ( TraceConsumer, ,  time.Duration) *Emitter {
	.ConsumeTimeUnit("ns")

	return &Emitter{
		c:          ,
		rangeStart: ,
		rangeEnd:   ,
		frameTree:  frameNode{children: make(map[uint64]frameNode)},
		resources:  make(map[uint64]string),
		tasks:      make(map[uint64]task),
	}
}

type Emitter struct {
	c          TraceConsumer
	rangeStart time.Duration
	rangeEnd   time.Duration

	heapStats, prevHeapStats     heapStats
	gstates, prevGstates         [gStateCount]int64
	threadStats, prevThreadStats [threadStateCount]int64
	gomaxprocs                   uint64
	frameTree                    frameNode
	frameSeq                     int
	arrowSeq                     uint64
	filter                       func(uint64) bool
	resourceType                 string
	resources                    map[uint64]string
	focusResource                uint64
	tasks                        map[uint64]task
	asyncSliceSeq                uint64
}

type task struct {
	name      string
	sortIndex int
}

func ( *Emitter) ( uint64) {
	if  > .gomaxprocs {
		.gomaxprocs = 
	}
}

func ( *Emitter) ( uint64,  string) {
	if .filter != nil && !.filter() {
		return
	}
	.resources[] = 
}

func ( *Emitter) ( string) {
	.resourceType = 
}

func ( *Emitter) ( func(uint64) bool) {
	.filter = 
}

func ( *Emitter) ( uint64,  string,  int) {
	.tasks[] = task{, }
}

func ( *Emitter) ( SliceEvent) {
	if .filter != nil && !.filter(.Resource) {
		return
	}
	.slice(, format.ProcsSection, "")
}

func ( *Emitter) ( SliceEvent) {
	.slice(, format.TasksSection, pickTaskColor(.Resource))
}

func ( *Emitter) ( SliceEvent,  uint64,  string) {
	if !.tsWithinRange(.Ts) && !.tsWithinRange(.Ts+.Dur) {
		return
	}
	.OptionalEvent(&format.Event{
		Name:     .Name,
		Phase:    "X",
		Time:     viewerTime(.Ts),
		Dur:      viewerTime(.Dur),
		PID:      ,
		TID:      .Resource,
		Stack:    .Stack,
		EndStack: .EndStack,
		Arg:      .Arg,
		Cname:    ,
	})
}

type SliceEvent struct {
	Name     string
	Ts       time.Duration
	Dur      time.Duration
	Resource uint64
	Stack    int
	EndStack int
	Arg      any
}

func ( *Emitter) ( AsyncSliceEvent) {
	if !.tsWithinRange(.Ts) && !.tsWithinRange(.Ts+.Dur) {
		return
	}
	if .filter != nil && !.filter(.Resource) {
		return
	}
	 := ""
	if .TaskColorIndex != 0 {
		 = pickTaskColor(.TaskColorIndex)
	}
	.asyncSliceSeq++
	.OptionalEvent(&format.Event{
		Category: .Category,
		Name:     .Name,
		Phase:    "b",
		Time:     viewerTime(.Ts),
		TID:      .Resource,
		ID:       .asyncSliceSeq,
		Scope:    .Scope,
		Stack:    .Stack,
		Cname:    ,
	})
	.OptionalEvent(&format.Event{
		Category: .Category,
		Name:     .Name,
		Phase:    "e",
		Time:     viewerTime(.Ts + .Dur),
		TID:      .Resource,
		ID:       .asyncSliceSeq,
		Scope:    .Scope,
		Stack:    .EndStack,
		Arg:      .Arg,
		Cname:    ,
	})
}

type AsyncSliceEvent struct {
	SliceEvent
	Category       string
	Scope          string
	TaskColorIndex uint64 // Take on the same color as the task with this ID.
}

func ( *Emitter) ( InstantEvent) {
	if !.tsWithinRange(.Ts) {
		return
	}
	if .filter != nil && !.filter(.Resource) {
		return
	}
	 := ""
	.OptionalEvent(&format.Event{
		Name:     .Name,
		Category: .Category,
		Phase:    "I",
		Scope:    "t",
		Time:     viewerTime(.Ts),
		PID:      format.ProcsSection,
		TID:      .Resource,
		Stack:    .Stack,
		Cname:    ,
		Arg:      .Arg,
	})
}

type InstantEvent struct {
	Ts       time.Duration
	Name     string
	Category string
	Resource uint64
	Stack    int
	Arg      any
}

func ( *Emitter) ( ArrowEvent) {
	if .filter != nil && (!.filter(.FromResource) || !.filter(.ToResource)) {
		return
	}
	.arrow(, format.ProcsSection)
}

func ( *Emitter) ( ArrowEvent) {
	.arrow(, format.TasksSection)
}

func ( *Emitter) ( ArrowEvent,  uint64) {
	if !.tsWithinRange(.Start) || !.tsWithinRange(.End) {
		return
	}
	.arrowSeq++
	.OptionalEvent(&format.Event{
		Name:  .Name,
		Phase: "s",
		TID:   .FromResource,
		PID:   ,
		ID:    .arrowSeq,
		Time:  viewerTime(.Start),
		Stack: .FromStack,
	})
	.OptionalEvent(&format.Event{
		Name:  .Name,
		Phase: "t",
		TID:   .ToResource,
		PID:   ,
		ID:    .arrowSeq,
		Time:  viewerTime(.End),
	})
}

type ArrowEvent struct {
	Name         string
	Start        time.Duration
	End          time.Duration
	FromResource uint64
	FromStack    int
	ToResource   uint64
}

func ( *Emitter) ( *format.Event) {
	.c.ConsumeViewerEvent(, true)
}

func ( *Emitter) ( time.Duration,  uint64) {
	.heapStats.heapAlloc = 
	.emitHeapCounters()
}

func ( *Emitter) ( uint64) {
	.focusResource = 
}

func ( *Emitter) ( time.Duration, ,  GState) {
	.gstates[]--
	.gstates[]++
	if .prevGstates == .gstates {
		return
	}
	if .tsWithinRange() {
		.OptionalEvent(&format.Event{
			Name:  "Goroutines",
			Phase: "C",
			Time:  viewerTime(),
			PID:   1,
			Arg: &format.GoroutineCountersArg{
				Running:   uint64(.gstates[GRunning]),
				Runnable:  uint64(.gstates[GRunnable]),
				GCWaiting: uint64(.gstates[GWaitingGC]),
			},
		})
	}
	.prevGstates = .gstates
}

func ( *Emitter) ( time.Duration,  ThreadState,  int64) {
	.threadStats[] += 
	if .prevThreadStats == .threadStats {
		return
	}
	if .tsWithinRange() {
		.OptionalEvent(&format.Event{
			Name:  "Threads",
			Phase: "C",
			Time:  viewerTime(),
			PID:   1,
			Arg: &format.ThreadCountersArg{
				Running:   int64(.threadStats[ThreadStateRunning]),
				InSyscall: int64(.threadStats[ThreadStateInSyscall]),
				// TODO(mknyszek): Why is InSyscallRuntime not included here?
			},
		})
	}
	.prevThreadStats = .threadStats
}

func ( *Emitter) ( time.Duration,  uint64) {
	// This cutoff at 1 PiB is a Workaround for https://github.com/golang/go/issues/63864.
	//
	// TODO(mknyszek): Remove this once the problem has been fixed.
	const  = 1 << 50
	if  >  {
		 = 0
	}
	.heapStats.nextGC = 
	.emitHeapCounters()
}

func ( *Emitter) ( time.Duration) {
	if .prevHeapStats == .heapStats {
		return
	}
	 := uint64(0)
	if .heapStats.nextGC > .heapStats.heapAlloc {
		 = .heapStats.nextGC - .heapStats.heapAlloc
	}
	if .tsWithinRange() {
		.OptionalEvent(&format.Event{
			Name:  "Heap",
			Phase: "C",
			Time:  viewerTime(),
			PID:   1,
			Arg:   &format.HeapCountersArg{Allocated: .heapStats.heapAlloc, NextGC: },
		})
	}
	.prevHeapStats = .heapStats
}

// Err returns an error if the emitter is in an invalid state.
func ( *Emitter) () error {
	if .gstates[GRunnable] < 0 || .gstates[GRunning] < 0 || .threadStats[ThreadStateInSyscall] < 0 || .threadStats[ThreadStateInSyscallRuntime] < 0 {
		return fmt.Errorf(
			"runnable=%d running=%d insyscall=%d insyscallRuntime=%d",
			.gstates[GRunnable],
			.gstates[GRunning],
			.threadStats[ThreadStateInSyscall],
			.threadStats[ThreadStateInSyscallRuntime],
		)
	}
	return nil
}

func ( *Emitter) ( time.Duration) bool {
	return .rangeStart <=  &&  <= .rangeEnd
}

// OptionalEvent emits ev if it's within the time range of of the consumer, i.e.
// the selected trace split range.
func ( *Emitter) ( *format.Event) {
	.c.ConsumeViewerEvent(, false)
}

func ( *Emitter) () {
	.processMeta(format.StatsSection, "STATS", 0)

	if len(.tasks) != 0 {
		.processMeta(format.TasksSection, "TASKS", 1)
	}
	for ,  := range .tasks {
		.threadMeta(format.TasksSection, , .name, .sortIndex)
	}

	.processMeta(format.ProcsSection, .resourceType, 2)

	.threadMeta(format.ProcsSection, trace.GCP, "GC", -6)
	.threadMeta(format.ProcsSection, trace.NetpollP, "Network", -5)
	.threadMeta(format.ProcsSection, trace.TimerP, "Timers", -4)
	.threadMeta(format.ProcsSection, trace.SyscallP, "Syscalls", -3)

	for ,  := range .resources {
		 := int()
		if .focusResource != 0 &&  == .focusResource {
			// Put the focus goroutine on top.
			 = -2
		}
		.threadMeta(format.ProcsSection, , , )
	}

	.c.Flush()
}

func ( *Emitter) (,  uint64,  string,  int) {
	.Event(&format.Event{
		Name:  "thread_name",
		Phase: "M",
		PID:   ,
		TID:   ,
		Arg:   &format.NameArg{Name: },
	})
	.Event(&format.Event{
		Name:  "thread_sort_index",
		Phase: "M",
		PID:   ,
		TID:   ,
		Arg:   &format.SortIndexArg{Index: },
	})
}

func ( *Emitter) ( uint64,  string,  int) {
	.Event(&format.Event{
		Name:  "process_name",
		Phase: "M",
		PID:   ,
		Arg:   &format.NameArg{Name: },
	})
	.Event(&format.Event{
		Name:  "process_sort_index",
		Phase: "M",
		PID:   ,
		Arg:   &format.SortIndexArg{Index: },
	})
}

// Stack emits the given frames and returns a unique id for the stack. No
// pointers to the given data are being retained beyond the call to Stack.
func ( *Emitter) ( []*trace.Frame) int {
	return .buildBranch(.frameTree, )
}

// buildBranch builds one branch in the prefix tree rooted at ctx.frameTree.
func ( *Emitter) ( frameNode,  []*trace.Frame) int {
	if len() == 0 {
		return .id
	}
	 := len() - 1
	 := []
	 = [:]

	,  := .children[.PC]
	if ! {
		.frameSeq++
		.id = .frameSeq
		.children = make(map[uint64]frameNode)
		.children[.PC] = 
		.c.ConsumeViewerFrame(strconv.Itoa(.id), format.Frame{Name: fmt.Sprintf("%v:%v", .Fn, .Line), Parent: .id})
	}
	return .(, )
}

type heapStats struct {
	heapAlloc uint64
	nextGC    uint64
}

func viewerTime( time.Duration) float64 {
	return float64() / float64(time.Microsecond)
}

type GState int

const (
	GDead GState = iota
	GRunnable
	GRunning
	GWaiting
	GWaitingGC

	gStateCount
)

type ThreadState int

const (
	ThreadStateInSyscall ThreadState = iota
	ThreadStateInSyscallRuntime
	ThreadStateRunning

	threadStateCount
)

type frameNode struct {
	id       int
	children map[uint64]frameNode
}

// Mapping from more reasonable color names to the reserved color names in
// https://github.com/catapult-project/catapult/blob/master/tracing/tracing/base/color_scheme.html#L50
// The chrome trace viewer allows only those as cname values.
const (
	colorLightMauve     = "thread_state_uninterruptible" // 182, 125, 143
	colorOrange         = "thread_state_iowait"          // 255, 140, 0
	colorSeafoamGreen   = "thread_state_running"         // 126, 200, 148
	colorVistaBlue      = "thread_state_runnable"        // 133, 160, 210
	colorTan            = "thread_state_unknown"         // 199, 155, 125
	colorIrisBlue       = "background_memory_dump"       // 0, 180, 180
	colorMidnightBlue   = "light_memory_dump"            // 0, 0, 180
	colorDeepMagenta    = "detailed_memory_dump"         // 180, 0, 180
	colorBlue           = "vsync_highlight_color"        // 0, 0, 255
	colorGrey           = "generic_work"                 // 125, 125, 125
	colorGreen          = "good"                         // 0, 125, 0
	colorDarkGoldenrod  = "bad"                          // 180, 125, 0
	colorPeach          = "terrible"                     // 180, 0, 0
	colorBlack          = "black"                        // 0, 0, 0
	colorLightGrey      = "grey"                         // 221, 221, 221
	colorWhite          = "white"                        // 255, 255, 255
	colorYellow         = "yellow"                       // 255, 255, 0
	colorOlive          = "olive"                        // 100, 100, 0
	colorCornflowerBlue = "rail_response"                // 67, 135, 253
	colorSunsetOrange   = "rail_animation"               // 244, 74, 63
	colorTangerine      = "rail_idle"                    // 238, 142, 0
	colorShamrockGreen  = "rail_load"                    // 13, 168, 97
	colorGreenishYellow = "startup"                      // 230, 230, 0
	colorDarkGrey       = "heap_dump_stack_frame"        // 128, 128, 128
	colorTawny          = "heap_dump_child_node_arrow"   // 204, 102, 0
	colorLemon          = "cq_build_running"             // 255, 255, 119
	colorLime           = "cq_build_passed"              // 153, 238, 102
	colorPink           = "cq_build_failed"              // 238, 136, 136
	colorSilver         = "cq_build_abandoned"           // 187, 187, 187
	colorManzGreen      = "cq_build_attempt_runnig"      // 222, 222, 75
	colorKellyGreen     = "cq_build_attempt_passed"      // 108, 218, 35
	colorAnotherGrey    = "cq_build_attempt_failed"      // 187, 187, 187
)

var colorForTask = []string{
	colorLightMauve,
	colorOrange,
	colorSeafoamGreen,
	colorVistaBlue,
	colorTan,
	colorMidnightBlue,
	colorIrisBlue,
	colorDeepMagenta,
	colorGreen,
	colorDarkGoldenrod,
	colorPeach,
	colorOlive,
	colorCornflowerBlue,
	colorSunsetOrange,
	colorTangerine,
	colorShamrockGreen,
	colorTawny,
	colorLemon,
	colorLime,
	colorPink,
	colorSilver,
	colorManzGreen,
	colorKellyGreen,
}

func pickTaskColor( uint64) string {
	 :=  % uint64(len(colorForTask))
	return colorForTask[]
}