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) }
// 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) } }