func compilerProxyLogSummary(w http.ResponseWriter, logPath string, cpl *compilerproxylog.CompilerProxyLog) error {
	data := struct {
		Path             string
		CompilerProxyLog *compilerproxylog.CompilerProxyLog
		Tasks            []*compilerproxylog.TaskLog
		NumTasks         int
		TasksPerSec      float64

		ByCompileMode map[compilerproxylog.CompileMode]byCompileMode

		DurationDistribution []time.Duration
	}{
		Path:             logPath,
		CompilerProxyLog: cpl,
		Tasks:            cpl.TaskLogs(),
		ByCompileMode:    make(map[compilerproxylog.CompileMode]byCompileMode),
	}
	data.NumTasks = len(data.Tasks)
	data.TasksPerSec = float64(data.NumTasks) / cpl.Duration().Seconds()
	var duration time.Duration
	for _, t := range data.Tasks {
		duration += t.Duration()
	}
	tasksByCompileMode := compilerproxylog.ClassifyByCompileMode(data.Tasks)
	for m, tasks := range tasksByCompileMode {
		mode := compilerproxylog.CompileMode(m)
		sort.Sort(sort.Reverse(compilerproxylog.ByDuration{TaskLogs: tasks}))
		bcm := byCompileMode{
			Tasks:    tasks,
			NumTasks: len(tasks),
		}
		if len(tasks) == 0 {
			data.ByCompileMode[mode] = bcm
			continue
		}
		if len(bcm.Tasks) > 10 {
			bcm.Tasks = bcm.Tasks[:10]
		}
		tr := compilerproxylog.ClassifyByResponse(tasks)
		var resps []string
		for r := range tr {
			resps = append(resps, r)
		}
		sort.Strings(resps)
		for _, r := range resps {
			bcm.Resps = append(bcm.Resps, struct {
				Response string
				Num      int
			}{
				Response: r,
				Num:      len(tr[r]),
			})
		}
		var duration time.Duration
		for _, t := range tasks {
			duration += t.Duration()
		}
		bcm.Average = duration / time.Duration(len(tasks))
		bcm.Max = tasks[0].Duration()
		for _, p := range []int{98, 91, 75, 50, 25, 9, 2} {
			bcm.P = append(bcm.P, struct {
				P int
				D time.Duration
			}{
				P: p,
				D: tasks[int(float64(len(tasks)*(100-p))/100.0)].Duration(),
			})
		}
		bcm.Min = tasks[len(tasks)-1].Duration()
		data.ByCompileMode[mode] = bcm
	}
	data.DurationDistribution = compilerproxylog.DurationDistribution(cpl.Created, data.Tasks)

	var buf bytes.Buffer
	err := compilerProxyLogIndexTempl.Execute(&buf, data)
	if err != nil {
		return err
	}
	w.Header().Set("Content-Type", "text/html")
	_, err = w.Write(buf.Bytes())
	return err
}
func diagnose(fname string) {
	f, err := os.Open(fname)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()
	rd, err := reader(fname, f)
	if err != nil {
		log.Fatal(err)
	}

	cpl, err := compilerproxylog.Parse(fname, rd)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Filename:", cpl.Filename)
	fmt.Println("Created:", cpl.Created)
	fmt.Println("Machine:", cpl.Machine)
	fmt.Println("GomaRevision:", cpl.GomaRevision)
	fmt.Println("GomaVersion:", cpl.GomaVersion)
	fmt.Println("GomaFlags:", cpl.GomaFlags)
	fmt.Println("GomaLimits:", cpl.GomaLimits)
	fmt.Println("CrashDump:", cpl.CrashDump)
	fmt.Println("Stats:", cpl.Stats)

	fmt.Println("")
	fmt.Println("duration:", cpl.Duration())

	tasks := cpl.TaskLogs()
	fmt.Println("tasks:", len(tasks))
	fmt.Println("tasks/sec:", float64(len(tasks))/cpl.Duration().Seconds())
	fmt.Println("")

	var duration time.Duration
	for _, t := range tasks {
		duration += t.Duration()
	}
	tasksByCompileMode := compilerproxylog.ClassifyByCompileMode(tasks)
	for i, tasks := range tasksByCompileMode {
		mode := compilerproxylog.CompileMode(i)
		fmt.Println(mode, ": # of tasks: ", len(tasks))
		if len(tasks) == 0 {
			fmt.Println("")
			continue
		}

		tr := compilerproxylog.ClassifyByResponse(tasks)
		var resps []string
		for r := range tr {
			resps = append(resps, r)
		}
		fmt.Println("  replies:")
		for _, r := range resps {
			fmt.Println("    ", r, len(tr[r]))
		}
		sort.Sort(sort.Reverse(compilerproxylog.ByDuration{TaskLogs: tasks}))
		var duration time.Duration
		for _, t := range tasks {
			duration += t.Duration()
		}
		fmt.Println("  durations:")
		fmt.Println("      ave  :", duration/time.Duration(len(tasks)))
		fmt.Println("      max  :", tasks[0].Duration())
		for _, q := range []int{98, 91, 75, 50, 25, 9, 2} {
			fmt.Printf("       %2d%% : %s\n", q, tasks[int(float64(len(tasks)*q)/100.0)].Duration())
		}
		fmt.Println("      min  :", tasks[len(tasks)-1].Duration())
		fmt.Println("  long tasks:")
		for i := 0; i < 5; i++ {
			if i >= len(tasks) {
				break
			}
			fmt.Printf("   #%d %s %s\n", i, tasks[i].ID, tasks[i].Duration())
			fmt.Println("    ", tasks[i].Desc)
			fmt.Println("    ", tasks[i].Response)
		}
		fmt.Println("")
	}

	dd := compilerproxylog.DurationDistribution(cpl.Created, tasks)
	fmt.Println("Duration per num active tasks")
	for i, d := range dd {
		fmt.Printf(" %3d tasks: %s\n", i, d)
	}
}