// Copyright 2015 Google Inc. All rights reserved // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package kati import ( "fmt" "io" "os" "sort" "sync" "time" ) type traceEventT struct { mu sync.Mutex f io.WriteCloser t0 time.Time pid int } const ( traceEventMain = iota + 1 // add new ones to use new goroutine. ) var traceEvent traceEventT // TraceEventStart starts trace event. func TraceEventStart(f io.WriteCloser) { traceEvent.start(f) } // TraceEventStop stops trace event. func TraceEventStop() { traceEvent.stop() } func (t *traceEventT) start(f io.WriteCloser) { t.f = f t.t0 = time.Now() fmt.Fprint(t.f, "[ ") } func (t *traceEventT) enabled() bool { return t.f != nil } func (t *traceEventT) stop() { fmt.Fprint(t.f, "\n]\n") t.f.Close() } type event struct { name, v string tid int t time.Time emit bool } func (t *traceEventT) begin(name string, v Value, tid int) event { var e event e.tid = tid e.t = time.Now() if t.f != nil || EvalStatsFlag { e.name = name e.v = v.String() } if t.f != nil { e.emit = name == "include" || name == "shell" if e.emit { t.emit("B", e, e.t.Sub(t.t0)) } } return e } func (t *traceEventT) emit(ph string, e event, ts time.Duration) { t.mu.Lock() defer t.mu.Unlock() if t.pid == 0 { t.pid = os.Getpid() } else { fmt.Fprintf(t.f, ",\n") } fmt.Fprintf(t.f, `{"pid":%d,"tid":%d,"ts":%d,"ph":%q,"cat":%q,"name":%q,"args":{}}`, t.pid, e.tid, ts.Nanoseconds()/1e3, ph, e.name, e.v, ) } func (t *traceEventT) end(e event) { if t.f != nil { if e.emit { t.emit("E", e, time.Since(t.t0)) } } stats.add(e.name, e.v, e.t) } type statsData struct { Name string Count int Longest time.Duration Total time.Duration } type statsT struct { mu sync.Mutex data map[string]statsData } var stats = &statsT{ data: make(map[string]statsData), } func (s *statsT) add(name, v string, t time.Time) { if !EvalStatsFlag { return } d := time.Since(t) key := fmt.Sprintf("%s:%s", name, v) s.mu.Lock() sd := s.data[key] if d > sd.Longest { sd.Longest = d } sd.Total += d sd.Count++ s.data[key] = sd s.mu.Unlock() } // DumpStats dumps statistics collected if EvalStatsFlag is set. func DumpStats() { if !EvalStatsFlag { return } var sv byTotalTime for k, v := range stats.data { v.Name = k sv = append(sv, v) } sort.Sort(sv) fmt.Println("count,longest(ns),total(ns),longest,total,name") for _, s := range sv { fmt.Printf("%d,%d,%d,%v,%v,%s\n", s.Count, s.Longest, s.Total, s.Longest, s.Total, s.Name) } } type byTotalTime []statsData func (b byTotalTime) Len() int { return len(b) } func (b byTotalTime) Swap(i, j int) { b[i], b[j] = b[j], b[i] } func (b byTotalTime) Less(i, j int) bool { return b[i].Total > b[j].Total } type shellStatsT struct { mu sync.Mutex duration time.Duration count int } var shellStats = &shellStatsT{} func (s *shellStatsT) add(d time.Duration) { s.mu.Lock() s.duration += d s.count++ s.mu.Unlock() } func (s *shellStatsT) Duration() time.Duration { s.mu.Lock() defer s.mu.Unlock() return s.duration } func (s *shellStatsT) Count() int { s.mu.Lock() defer s.mu.Unlock() return s.count }