func main() {
	flag.Parse()

	c, err := ioutil.ReadFile(*inputFile)
	if err != nil {
		log.Fatalf("Cannot open the file %s: %v", *inputFile, err)
	}

	br, fname, err := bugreportutils.ExtractBugReport(*inputFile, c)
	if err != nil {
		log.Fatalf("Error getting file contents: %v", err)
	}
	fmt.Printf("Parsing %s\n", fname)
	bs := bugreportutils.ExtractBatterystatsCheckin(br)
	if strings.Contains(bs, "Exception occurred while dumping") {
		log.Fatalf("Exception found in battery dump.")
	}
	m, err := bugreportutils.ParseMetaInfo(br)
	if err != nil {
		log.Fatalf("Unable to get meta info: %v", err)
	}
	s := &sessionpb.Checkin{
		Checkin:          proto.String(bs),
		BuildFingerprint: proto.String(m.BuildFingerprint),
	}
	pkgs, errs := packageutils.ExtractAppsFromBugReport(br)
	if len(errs) > 0 {
		log.Fatalf("Errors encountered when getting package list: %v", errs)
	}

	var ctr checkinutil.IntCounter
	stats, warns, errs := checkinparse.ParseBatteryStats(&ctr, checkinparse.CreateCheckinReport(s), pkgs)
	if len(warns) > 0 {
		log.Printf("Encountered unexpected warnings: %v\n", warns)
	}
	if len(errs) > 0 {
		log.Fatalf("Could not parse battery stats: %v\n", errs)
	}
	fmt.Println("\n################\n")
	fmt.Println("Partial Wakelocks")
	fmt.Println("################\n")
	var pwl []*checkinparse.WakelockInfo
	for _, app := range stats.App {
		for _, pw := range app.Wakelock {
			if pw.GetPartialTimeMsec() > 0 {
				pwl = append(pwl,
					&checkinparse.WakelockInfo{
						Name:     fmt.Sprintf("%s : %s", app.GetName(), pw.GetName()),
						UID:      app.GetUid(),
						Duration: time.Duration(pw.GetPartialTimeMsec()) * time.Millisecond,
					})
			}
		}

	}
	checkinparse.SortByTime(pwl)
	for _, pw := range pwl[:min(5, len(pwl))] {
		fmt.Printf("%s (uid=%d) %s\n", pw.Duration, pw.UID, pw.Name)
	}

	fmt.Println("\n################")
	fmt.Println("Kernel Wakelocks")
	fmt.Println("################\n")
	var kwl []*checkinparse.WakelockInfo
	for _, kw := range stats.System.KernelWakelock {
		if kw.GetName() != "PowerManagerService.WakeLocks" && kw.GetTimeMsec() > 0 {
			kwl = append(kwl, &checkinparse.WakelockInfo{
				Name:     kw.GetName(),
				Duration: time.Duration(kw.GetTimeMsec()) * time.Millisecond,
			})
		}
	}
	checkinparse.SortByTime(kwl)
	for _, kw := range kwl[:min(5, len(kwl))] {
		fmt.Printf("%s %s\n", kw.Duration, kw.Name)
	}

	data, err := proto.Marshal(stats)
	if err != nil {
		log.Fatalf("Error from proto.Marshal: %v", err)
	}
	ioutil.WriteFile("checkin.proto", data, 0600)
}
Example #2
0
// parseBugReport analyzes the given bug report contents, and updates the ParsedData object.
func (pd *ParsedData) parseBugReport(fname, contents string) error {
	meta, err := bugreportutils.ParseMetaInfo(contents)
	if err != nil {
		// If there are issues getting the meta info, then the file is most likely not a bug report.
		return errors.New("error parsing the bug report. Please provide a well formed bug report")
	}

	log.Printf("Trace started analyzing file.")
	// Generate the Historian plot and parse batterystats and activity manager simultaneously.
	historianCh := make(chan historianData)
	summariesCh := make(chan summariesData)
	checkinCh := make(chan checkinData)
	activityManagerCh := make(chan activityManagerData)

	// Create a temporary file to save the bug report, for the Historian script.
	brFile, err := writeTempFile(contents)

	historianOutput := historianData{"", err}
	if err == nil {
		// Don't run the Historian script if could not create temporary file.
		defer os.Remove(brFile)
		go func() {
			html, err := generateHistorianPlot(fname, brFile)
			historianCh <- historianData{html, err}
			log.Printf("Trace finished generating Historian plot.")
		}()
	}

	var errs []error
	ce := ""
	if meta.SdkVersion < minSupportedSDK {
		ce = "Unsupported bug report version."
		errs = append(errs, errors.New("unsupported bug report version"))
	} else {
		// No point running these if we don't support the sdk version since we won't get any data from them.
		bs := bugreportutils.ExtractBatterystatsCheckin(contents)

		if strings.Contains(bs, "Exception occurred while dumping") {
			// TODO: Display activity manager events even if battery data is invalid. Currently they will not be displayed.
			ce = "Exception found in battery dump."
			errs = append(errs, errors.New("exception found in battery dump"))
			close(summariesCh)
			close(checkinCh)
		} else {
			pkgs, pkgErrs := packageutils.ExtractAppsFromBugReport(contents)
			errs = append(errs, pkgErrs...)

			// Activity manager events are only parsed for supported sdk versions, even though they are still present in unsupported sdk version reports.
			// This is as the events are rendered with Historian v2, which is not generated for unsupported sdk versions.
			go func() {
				amCSV, warnings, errs := activity.Parse(pkgs, contents)
				activityManagerCh <- activityManagerData{amCSV, warnings, errs}
			}()

			go func() {
				var ctr checkinutil.IntCounter

				s := &sessionpb.Checkin{
					Checkin:          proto.String(bs),
					BuildFingerprint: proto.String(meta.BuildFingerprint),
				}
				stats, warnings, pbsErrs := checkinparse.ParseBatteryStats(&ctr, checkinparse.CreateCheckinReport(s), pkgs)
				checkinCh <- checkinData{stats, warnings, pbsErrs}
				log.Printf("Trace finished processing checkin.")
				if stats == nil {
					ce = "Could not parse aggregated battery stats."
					errs = append(errs, errors.New("could not parse aggregated battery stats"))
					// Only returning from this goroutine.
					return
				}
				pd.deviceType = stats.GetBuild().GetDevice()
			}()

			go func() {
				summariesCh <- analyze(bs, pkgs)
				log.Printf("Trace finished processing summary data.")
			}()
		}
	}

	if historianOutput.err == nil {
		historianOutput = <-historianCh
	}
	if historianOutput.err != nil {
		historianOutput.html = fmt.Sprintf("Error generating historian plot: %v", historianOutput.err)
	}

	var summariesOutput summariesData
	var checkinOutput checkinData
	var activityManagerOutput activityManagerData
	if meta.SdkVersion >= minSupportedSDK {
		summariesOutput = <-summariesCh
		checkinOutput = <-checkinCh
		activityManagerOutput = <-activityManagerCh
		errs = append(errs, append(summariesOutput.errs, append(activityManagerOutput.errs, checkinOutput.err...)...)...)
	}

	log.Printf("Trace finished generating Historian plot and summaries.")
	var loc string
	if d, err := bugreportutils.DumpState(contents); err != nil {
		log.Printf("Failed to extract time information from bugreport dumpstate: %v", err)
	} else {
		loc = d.Location().String()
	}

	warnings := append(checkinOutput.warnings, activityManagerOutput.warnings...)
	data := presenter.Data(meta,
		fname, summariesOutput.summaries,
		checkinOutput.batterystats, historianOutput.html,
		warnings,
		errs, summariesOutput.overflow)

	pd.responseArr = append(pd.responseArr, uploadResponse{
		SDKVersion:      data.SDKVersion,
		HistorianV2CSV:  appendCSVs(summariesOutput.historianV2CSV, activityManagerOutput.csv),
		LevelSummaryCSV: summariesOutput.levelSummaryCSV,
		ReportVersion:   data.CheckinSummary.ReportVersion,
		AppStats:        data.AppStats,
		DeviceCapacity:  checkinOutput.batterystats.GetSystem().GetPowerUseSummary().GetBatteryCapacityMah(),
		HistogramStats:  extractHistogramStats(data),
		TimeToDelta:     summariesOutput.timeToDelta,
		CriticalError:   ce,
		FileName:        data.Filename,
		Location:        loc,
	})
	pd.data = append(pd.data, data)
	return nil
}
// UploadHandler is the main analysis function.
func UploadHandler(w http.ResponseWriter, r *http.Request) {
	// If false, the upload template will load closure and js files in the header.
	uploadData := struct {
		IsOptimizedJs bool
	}{
		isOptimizedJs,
	}

	switch r.Method {
	//GET displays the upload form.
	case "GET":
		if err := uploadTempl.Execute(w, uploadData); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

	//POST takes the uploaded file(s) and saves it to disk.
	case "POST":
		// Do not accept files that are greater than 50 MBs
		if r.ContentLength > maxFileSize {
			closeConnection(w, "File too large (>50MB).")
			return
		}
		r.Body = http.MaxBytesReader(w, r.Body, maxFileSize)

		//get the multipart reader for the request.
		reader, err := r.MultipartReader()
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		log.Printf("Trace Starting reading uploaded file. %d bytes", r.ContentLength)

		//copy each part to destination.
		for {
			part, err := reader.NextPart()
			if err == io.EOF {
				break
			}

			//if part.FileName() is empty, skip this iteration.
			if part == nil || part.FileName() == "" {
				continue
			}

			b, err := ioutil.ReadAll(part)
			if err != nil {
				http.Error(w, "Failed to read file. Please try again.", http.StatusInternalServerError)
				return
			}

			contentType := http.DetectContentType(b)
			if !strings.Contains(contentType, "text/plain") {
				http.Error(w, "Incorrect file format detected", http.StatusInternalServerError)
				return
			}

			log.Printf("Trace started analyzing file.")

			// Generate the Historian plot and parsing simultaneously.
			historianCh := make(chan historianData)
			summariesCh := make(chan summariesData)
			checkinCh := make(chan checkinData)

			contents := string(b)
			// Create a temporary file to save bug report, for the Historian script.
			tmpFile, err := ioutil.TempFile("", "historian")

			historianOutput := historianData{"", err}
			if err == nil {
				// Don't run the Historian script if could not create temporary file.
				fname := tmpFile.Name()
				defer os.Remove(fname)
				tmpFile.WriteString(contents)
				tmpFile.Close()
				go func() {
					html, err := generateHistorianPlot(w, part.FileName(), fname)
					historianCh <- historianData{html, err}
					log.Printf("Trace finished generating Historian plot.")
				}()
			}

			var errs []error
			sdk, err := sdkVersion(contents)
			if sdk < minSupportedSDK {
				errs = append(errs, errors.New("unsupported bug report version"))
			}
			if err != nil {
				errs = append(errs, err)
			}

			if sdk >= minSupportedSDK {
				// No point running these if we don't support the sdk version since we won't get any data from them.
				go func() {
					o, c, errs := analyze(contents)
					summariesCh <- summariesData{o, c, errs}
					log.Printf("Trace finished processing summary data.")
				}()

				go func() {
					var ctr checkinutil.IntCounter

					/* Extract Build Fingerprint from the bugreport. */
					s := &sessionpb.Checkin{
						Checkin:          proto.String(contents),
						BuildFingerprint: proto.String(extractBuildFingerprint(contents)),
					}
					pkgs, pkgErrs := packageutils.ExtractAppsFromBugReport(contents)
					stats, warnings, pbsErrs := checkinparse.ParseBatteryStats(&ctr, checkinparse.CreateCheckinReport(s), pkgs)
					checkinCh <- checkinData{stats, warnings, append(pkgErrs, pbsErrs...)}
				}()
			}

			if historianOutput.err == nil {
				historianOutput = <-historianCh
			}
			if historianOutput.err != nil {
				historianOutput.html = fmt.Sprintf("Error generating historian plot: %v", historianOutput.err)
			}

			var summariesOutput summariesData
			var checkinOutput checkinData
			if sdk >= minSupportedSDK {
				summariesOutput = <-summariesCh
				checkinOutput = <-checkinCh
				errs = append(errs, append(summariesOutput.errs, checkinOutput.err...)...)
			}

			log.Printf("Trace finished generating Historian plot and summaries.")

			data := presenter.Data(sdk, modelName(contents), summariesOutput.historianCsv, part.FileName(), summariesOutput.summaries, checkinOutput.batterystats, historianOutput.html, checkinOutput.warnings, errs)

			if err := resultTempl.Execute(w, data); err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
			}

			log.Printf("Trace ended analyzing file.")
		}
	default:
		w.WriteHeader(http.StatusMethodNotAllowed)
	}
}
func main() {
	flag.Parse()

	inputs := strings.Split(*inputFiles, ",")
	if len(inputs) != 2 {
		log.Fatal("wrong input file number, expect 2, got ", len(inputs))
	}

	dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
	if err != nil {
		log.Fatal(err)
	}
	stats := make([]*bspb.BatteryStats, 2)
	normalizedStats := make([]*bspb.BatteryStats, 2)

	var errs []error
	var warns []string
	var ctr checkinutil.IntCounter
	for i, f := range inputs {
		c, err := ioutil.ReadFile(f)
		if err != nil {
			log.Fatalf("Cannot open the file %s: %v", f, err)
		}
		br, fname, err := bugreportutils.ExtractBugReport(f, c)
		if err != nil {
			log.Fatalf("Error getting file contents: %v", err)
		}
		fmt.Printf("** File #%d: %s\n", i, fname)
		bs := bugreportutils.ExtractBatterystatsCheckin(br)
		if strings.Contains(bs, "Exception occurred while dumping") {
			log.Fatalf("Exception found in battery dump.")
		}
		s := &sessionpb.Checkin{Checkin: proto.String(bs)}

		stats[i], warns, errs = checkinparse.ParseBatteryStats(&ctr, checkinparse.CreateCheckinReport(s), nil)

		if len(errs) > 0 {
			log.Fatalf("Could not parse battery stats: %v", errs)
		}
		if len(warns) > 0 {
			fmt.Printf("Encountered unexpected warnings: %v\n", warns)
		}

		data, err := proto.Marshal(stats[i])
		if err != nil {
			log.Fatalf("Cannot marshal input proto: %v", err)
		}

		if *doDelta {
			ioutil.WriteFile(*parseFileName+strconv.Itoa(i)+".rawproto", data, 0600)
			ioutil.WriteFile(dir+"/"+*parseFileName+strconv.Itoa(i)+".rawproto", data, 0600)
		}

		if *doComp {
			n, err := checkindelta.NormalizeStats(stats[i])
			if err != nil {
				log.Fatalf("Failed to normalize: %v", err)
			}
			normalizedStats[i] = n

			normData, err := proto.Marshal(normalizedStats[i])
			if err != nil {
				log.Fatalf("Cannot marshal normalized input proto: %v", err)
			}
			ioutil.WriteFile(*normalizedFileName+strconv.Itoa(i)+".rawproto", normData, 0600)
			ioutil.WriteFile(dir+"/"+*normalizedFileName+strconv.Itoa(i)+".rawproto", normData, 0600)
		}
	}

	if *doComp {
		fmt.Printf("\n\nNormalized Delta Report (File1 - File2): \n\n")
		for i, f := range inputs {
			fmt.Printf("File %d: %v\n", i+1, f)
		}

		outputProto := checkindelta.ComputeDelta(normalizedStats[0], normalizedStats[1])
		if outputProto == nil {
			log.Fatalf("empty result")
		}
		printResults(outputProto, *compareFileName, dir)
	}
	if *doDelta {
		fmt.Printf("\n\nDelta Report(File1 - File2)- \n\n")
		for i, f := range inputs {
			fmt.Printf("File %d: %v\n", i+1, f)
		}
		outputProto := checkindelta.ComputeDelta(stats[0], stats[1])
		if outputProto == nil {
			log.Fatalf("empty result")
		}
		printResults(outputProto, *deltaFileName, dir)
	}
}