blob: e3af9a53dac394c5c3e1568c98fd5873f5d1d96e [file] [log] [blame]
// Copyright 2017 The Fuchsia 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 main
import (
"encoding/json"
"flag"
"fmt"
"io"
"log"
"math"
"os"
"sort"
"strings"
humanize "github.com/dustin/go-humanize"
)
type EventArgs struct {
Count uint64
Value uint64
}
type Event struct {
Args EventArgs
Cat string
Id string
Name string
Ph string
Tid uint
Ts float64
}
type TraceJson struct {
TraceEvents []Event
}
// This is used to compute the lapsed time of the count: We're given two
// records: one at start-time and one at stop-time.
type EventKey struct {
Cat string
Id string
Ph string
Tid uint
}
func makeEventKey(event Event) EventKey {
return EventKey{event.Cat,
event.Id,
event.Ph,
event.Tid}
}
type RecordType int
const (
CountRecordType RecordType = iota
ValueRecordType
)
type CpuRecord struct {
Name string
Id string
Type RecordType
Value uint64
// Duration over which the count applies or zero for non-counter
// events.
Duration float64
}
// This is for sorting CpuRecord by ids.
type CpuRecordArray []CpuRecord
func (a CpuRecordArray) Len() int { return len(a) }
func (a CpuRecordArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a CpuRecordArray) Less(i, j int) bool { return a[i].Id < a[j].Id }
func commaFormat(n uint64) string {
// humanize takes int64s. If we get a number that big don't print
// garbage, just don't comma-ize it.
if n > math.MaxInt64 {
return fmt.Sprintf("%d", n)
}
return humanize.Comma(int64(n))
}
var (
outputFile string
title string
)
func init() {
flag.StringVar(&outputFile, "output", "", "Where to write the result to")
flag.StringVar(&title, "title", "Table of events", "The title of the report")
}
func main() {
log.SetFlags(0)
log.SetPrefix("tally: ")
log.Print("started")
flag.Parse()
if flag.NArg() > 1 {
log.Fatal("Too many arguments")
}
inputFile := flag.Arg(0)
var in io.Reader
var out io.Writer
if inputFile != "" {
f, err := os.Open(inputFile)
if err != nil {
log.Fatalln("Unable to open input file:", err)
}
in = io.Reader(f)
defer f.Close()
} else {
in = os.Stdin
}
if outputFile != "" {
f, err := os.Create(outputFile)
if err != nil {
log.Fatalln("Unable to open output file:", err)
}
out = io.Writer(f)
defer f.Close()
} else {
out = os.Stdout
}
decoder := json.NewDecoder(in)
var json TraceJson
if err := decoder.Decode(&json); err != nil {
log.Fatalln("Error reading json input:", err)
return
}
fmt.Fprintf(out, "%s\n", title)
startEvents := make(map[EventKey]Event)
endEvents := make(map[EventKey]Event)
perCpuRecords := make(map[uint]CpuRecordArray)
// First pass: Collect data for each cpu.
for _, event := range json.TraceEvents {
if !strings.HasPrefix(event.Cat, "cpu:") {
continue
}
if (event.Ph != "C") {
continue;
}
key := makeEventKey(event)
start, start_ok := startEvents[key]
if !start_ok {
startEvents[key] = event
continue
}
// TODO(dje): For now we assume there are only two records
// for each event: start and end. Instead accumulate the
// results for counter events.
_, end_ok := endEvents[key]
if end_ok {
log.Println("WARNING: unexpected extra record")
continue
}
endEvents[key] = event
cpu := event.Tid
// Assume we have a value, unless Count is non-zero.
// If they're both zero we don't want to print the value
// as a rate.
countOrValue := event.Args.Value
recordType := ValueRecordType
if (event.Args.Count != 0) {
recordType = CountRecordType
countOrValue = event.Args.Count
}
if recordType == CountRecordType {
countOrValue = countOrValue - start.Args.Count
}
perCpuRecords[cpu] = append(perCpuRecords[cpu], CpuRecord{
event.Name,
event.Id,
recordType,
countOrValue,
event.Ts - start.Ts})
}
// Value records are only emitted once, for the end value.
// We don't process value records in the above loop because if the
// value is zero we can't tell if the record is the initial count
// record.
for key, event := range startEvents {
_, end_ok := endEvents[key]
if !end_ok {
// A non-zero count field for the first record
// is an error.
if event.Args.Count != 0 {
continue
}
cpu := event.Tid
perCpuRecords[cpu] = append(perCpuRecords[cpu], CpuRecord{
event.Name,
event.Id,
ValueRecordType,
event.Args.Value,
event.Ts})
}
}
// Second pass: Print
var cpus []uint
for k := range perCpuRecords {
cpus = append(cpus, k)
}
sort.Slice(cpus, func(i, j int) bool { return cpus[i] < cpus[j] })
for _, cpu := range cpus {
if cpu == 0 {
fmt.Fprintf(out, "System-wide\n")
} else {
fmt.Fprintf(out, "Cpu %d\n", cpu-1)
}
records := perCpuRecords[cpu]
sort.Sort(records)
for _, record := range records {
value_string := fmt.Sprintf("%16s", commaFormat(record.Value))
if record.Type == CountRecordType && record.Duration != 0 {
microsecsPerSec := uint64(1000 * 1000)
value_string += fmt.Sprintf(
" (%s/sec)",
commaFormat((record.Value*microsecsPerSec)/uint64(record.Duration)))
}
fmt.Fprintf(out, " %-38s %s\n",
value_string, record.Name)
}
}
log.Print("done")
os.Exit(0)
}