combine.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. package main
  2. import (
  3. "bufio"
  4. "bytes"
  5. "fmt"
  6. "io"
  7. "io/ioutil"
  8. "os"
  9. "runtime/debug"
  10. "sort"
  11. "strings"
  12. "github.com/gogo/protobuf/proto"
  13. "github.com/spf13/cobra"
  14. progress "gopkg.in/cheggaaa/pb.v1"
  15. "gopkg.in/src-d/hercules.v7"
  16. "gopkg.in/src-d/hercules.v7/internal/pb"
  17. )
  18. // combineCmd represents the combine command
  19. var combineCmd = &cobra.Command{
  20. Use: "combine",
  21. Short: "Merge several binary analysis results together.",
  22. Long: ``,
  23. Args: cobra.MinimumNArgs(1),
  24. Run: func(cmd *cobra.Command, files []string) {
  25. if len(files) == 1 {
  26. file, err := os.Open(files[0])
  27. if err != nil {
  28. panic(err)
  29. }
  30. defer file.Close()
  31. io.Copy(os.Stdout, bufio.NewReader(file))
  32. return
  33. }
  34. var repos []string
  35. allErrors := map[string][]string{}
  36. mergedResults := map[string]interface{}{}
  37. mergedMetadata := &hercules.CommonAnalysisResult{}
  38. var fileName string
  39. bar := progress.New(len(files))
  40. bar.Callback = func(msg string) {
  41. os.Stderr.WriteString("\033[2K\r" + msg + " " + fileName)
  42. }
  43. bar.NotPrint = true
  44. bar.ShowPercent = false
  45. bar.ShowSpeed = false
  46. bar.SetMaxWidth(80).Start()
  47. debug.SetGCPercent(20)
  48. for _, fileName = range files {
  49. bar.Increment()
  50. anotherResults, anotherMetadata, errs := loadMessage(fileName, &repos)
  51. if anotherMetadata != nil {
  52. mergeResults(mergedResults, mergedMetadata, anotherResults, anotherMetadata)
  53. }
  54. allErrors[fileName] = errs
  55. debug.FreeOSMemory()
  56. }
  57. bar.Finish()
  58. os.Stderr.WriteString("\033[2K\r")
  59. printErrors(allErrors)
  60. sort.Strings(repos)
  61. if mergedMetadata == nil {
  62. return
  63. }
  64. mergedMessage := pb.AnalysisResults{
  65. Header: &pb.Metadata{
  66. Version: int32(hercules.BinaryVersion),
  67. Hash: hercules.BinaryGitHash,
  68. Repository: strings.Join(repos, " & "),
  69. },
  70. Contents: map[string][]byte{},
  71. }
  72. mergedMetadata.FillMetadata(mergedMessage.Header)
  73. for key, val := range mergedResults {
  74. buffer := bytes.Buffer{}
  75. err := hercules.Registry.Summon(key)[0].(hercules.LeafPipelineItem).Serialize(
  76. val, true, &buffer)
  77. if err != nil {
  78. panic(err)
  79. }
  80. mergedMessage.Contents[key] = buffer.Bytes()
  81. }
  82. serialized, err := proto.Marshal(&mergedMessage)
  83. if err != nil {
  84. panic(err)
  85. }
  86. os.Stdout.Write(serialized)
  87. },
  88. }
  89. func loadMessage(fileName string, repos *[]string) (
  90. map[string]interface{}, *hercules.CommonAnalysisResult, []string) {
  91. var errs []string
  92. fi, err := os.Stat(fileName)
  93. if err != nil {
  94. errs = append(errs, "Cannot access "+fileName+": "+err.Error())
  95. return nil, nil, errs
  96. }
  97. if fi.Size() == 0 {
  98. errs = append(errs, "Cannot parse "+fileName+": file size is 0")
  99. return nil, nil, errs
  100. }
  101. buffer, err := ioutil.ReadFile(fileName)
  102. if err != nil {
  103. errs = append(errs, "Cannot read "+fileName+": "+err.Error())
  104. return nil, nil, errs
  105. }
  106. message := pb.AnalysisResults{}
  107. err = proto.Unmarshal(buffer, &message)
  108. if err != nil {
  109. errs = append(errs, "Cannot parse "+fileName+": "+err.Error())
  110. return nil, nil, errs
  111. }
  112. if message.Header == nil {
  113. errs = append(errs, "Cannot parse "+fileName+": corrupted header")
  114. return nil, nil, errs
  115. }
  116. *repos = append(*repos, message.Header.Repository)
  117. results := map[string]interface{}{}
  118. for key, val := range message.Contents {
  119. summoned := hercules.Registry.Summon(key)
  120. if len(summoned) == 0 {
  121. errs = append(errs, fileName+": item not found: "+key)
  122. continue
  123. }
  124. mpi, ok := summoned[0].(hercules.ResultMergeablePipelineItem)
  125. if !ok {
  126. errs = append(errs, fileName+": "+key+": ResultMergeablePipelineItem is not implemented")
  127. continue
  128. }
  129. msg, err := mpi.Deserialize(val)
  130. if err != nil {
  131. errs = append(errs, fileName+": deserialization failed: "+key+": "+err.Error())
  132. continue
  133. }
  134. results[key] = msg
  135. }
  136. return results, hercules.MetadataToCommonAnalysisResult(message.Header), errs
  137. }
  138. func printErrors(allErrors map[string][]string) {
  139. needToPrintErrors := false
  140. for _, errs := range allErrors {
  141. if len(errs) > 0 {
  142. needToPrintErrors = true
  143. break
  144. }
  145. }
  146. if !needToPrintErrors {
  147. return
  148. }
  149. fmt.Fprintln(os.Stderr, "Errors:")
  150. for key, errs := range allErrors {
  151. if len(errs) > 0 {
  152. fmt.Fprintln(os.Stderr, " "+key)
  153. for _, err := range errs {
  154. fmt.Fprintln(os.Stderr, " "+err)
  155. }
  156. }
  157. }
  158. }
  159. func mergeResults(mergedResults map[string]interface{},
  160. mergedCommons *hercules.CommonAnalysisResult,
  161. anotherResults map[string]interface{},
  162. anotherCommons *hercules.CommonAnalysisResult) {
  163. for key, val := range anotherResults {
  164. mergedResult, exists := mergedResults[key]
  165. if !exists {
  166. mergedResults[key] = val
  167. continue
  168. }
  169. item := hercules.Registry.Summon(key)[0].(hercules.ResultMergeablePipelineItem)
  170. mergedResult = item.MergeResults(mergedResult, val, mergedCommons, anotherCommons)
  171. mergedResults[key] = mergedResult
  172. }
  173. if mergedCommons.CommitsNumber == 0 {
  174. *mergedCommons = *anotherCommons
  175. } else {
  176. mergedCommons.Merge(anotherCommons)
  177. }
  178. }
  179. func init() {
  180. rootCmd.AddCommand(combineCmd)
  181. combineCmd.SetUsageFunc(combineCmd.UsageFunc())
  182. }