combine.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  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.v10"
  16. "gopkg.in/src-d/hercules.v10/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. _, err = io.Copy(os.Stdout, bufio.NewReader(file))
  32. if err != nil {
  33. panic(err)
  34. }
  35. return
  36. }
  37. only, err := cmd.Flags().GetString("only")
  38. if err != nil {
  39. panic(err)
  40. }
  41. var repos []string
  42. allErrors := map[string][]string{}
  43. mergedResults := map[string]interface{}{}
  44. mergedMetadata := &hercules.CommonAnalysisResult{}
  45. var fileName string
  46. bar := progress.New(len(files))
  47. bar.Callback = func(msg string) {
  48. os.Stderr.WriteString("\033[2K\r" + msg + " " + fileName)
  49. }
  50. bar.NotPrint = true
  51. bar.ShowPercent = false
  52. bar.ShowSpeed = false
  53. bar.SetMaxWidth(80).Start()
  54. debug.SetGCPercent(20)
  55. for _, fileName = range files {
  56. bar.Increment()
  57. anotherResults, anotherMetadata, errs := loadMessage(fileName, &repos)
  58. if anotherMetadata != nil {
  59. mergeResults(mergedResults, mergedMetadata, anotherResults, anotherMetadata, only)
  60. }
  61. allErrors[fileName] = errs
  62. debug.FreeOSMemory()
  63. }
  64. bar.Finish()
  65. os.Stderr.WriteString("\033[2K\r")
  66. printErrors(allErrors)
  67. sort.Strings(repos)
  68. if mergedMetadata == nil {
  69. return
  70. }
  71. mergedMessage := pb.AnalysisResults{
  72. Header: &pb.Metadata{
  73. Version: int32(hercules.BinaryVersion),
  74. Hash: hercules.BinaryGitHash,
  75. Repository: strings.Join(repos, " & "),
  76. },
  77. Contents: map[string][]byte{},
  78. }
  79. mergedMetadata.FillMetadata(mergedMessage.Header)
  80. for key, val := range mergedResults {
  81. buffer := bytes.Buffer{}
  82. err := hercules.Registry.Summon(key)[0].(hercules.LeafPipelineItem).Serialize(
  83. val, true, &buffer)
  84. if err != nil {
  85. panic(err)
  86. }
  87. mergedMessage.Contents[key] = buffer.Bytes()
  88. }
  89. serialized, err := proto.Marshal(&mergedMessage)
  90. if err != nil {
  91. panic(err)
  92. }
  93. os.Stdout.Write(serialized)
  94. },
  95. }
  96. func loadMessage(fileName string, repos *[]string) (
  97. map[string]interface{}, *hercules.CommonAnalysisResult, []string) {
  98. var errs []string
  99. fi, err := os.Stat(fileName)
  100. if err != nil {
  101. errs = append(errs, "Cannot access "+fileName+": "+err.Error())
  102. return nil, nil, errs
  103. }
  104. if fi.Size() == 0 {
  105. errs = append(errs, "Cannot parse "+fileName+": file size is 0")
  106. return nil, nil, errs
  107. }
  108. buffer, err := ioutil.ReadFile(fileName)
  109. if err != nil {
  110. errs = append(errs, "Cannot read "+fileName+": "+err.Error())
  111. return nil, nil, errs
  112. }
  113. message := pb.AnalysisResults{}
  114. err = proto.Unmarshal(buffer, &message)
  115. if err != nil {
  116. errs = append(errs, "Cannot parse "+fileName+": "+err.Error())
  117. return nil, nil, errs
  118. }
  119. if message.Header == nil {
  120. errs = append(errs, "Cannot parse "+fileName+": corrupted header")
  121. return nil, nil, errs
  122. }
  123. *repos = append(*repos, message.Header.Repository)
  124. results := map[string]interface{}{}
  125. for key, val := range message.Contents {
  126. summoned := hercules.Registry.Summon(key)
  127. if len(summoned) == 0 {
  128. errs = append(errs, fileName+": item not found: "+key)
  129. continue
  130. }
  131. mpi, ok := summoned[0].(hercules.ResultMergeablePipelineItem)
  132. if !ok {
  133. errs = append(errs, fileName+": "+key+": ResultMergeablePipelineItem is not implemented")
  134. continue
  135. }
  136. msg, err := mpi.Deserialize(val)
  137. if err != nil {
  138. errs = append(errs, fileName+": deserialization failed: "+key+": "+err.Error())
  139. continue
  140. }
  141. results[key] = msg
  142. }
  143. return results, hercules.MetadataToCommonAnalysisResult(message.Header), errs
  144. }
  145. func printErrors(allErrors map[string][]string) {
  146. needToPrintErrors := false
  147. for _, errs := range allErrors {
  148. if len(errs) > 0 {
  149. needToPrintErrors = true
  150. break
  151. }
  152. }
  153. if !needToPrintErrors {
  154. return
  155. }
  156. fmt.Fprintln(os.Stderr, "Errors:")
  157. for key, errs := range allErrors {
  158. if len(errs) > 0 {
  159. fmt.Fprintln(os.Stderr, " "+key)
  160. for _, err := range errs {
  161. fmt.Fprintln(os.Stderr, " "+err)
  162. }
  163. }
  164. }
  165. }
  166. func mergeResults(mergedResults map[string]interface{},
  167. mergedCommons *hercules.CommonAnalysisResult,
  168. anotherResults map[string]interface{},
  169. anotherCommons *hercules.CommonAnalysisResult,
  170. only string) {
  171. for key, val := range anotherResults {
  172. if only != "" && key != only {
  173. continue
  174. }
  175. mergedResult, exists := mergedResults[key]
  176. if !exists {
  177. mergedResults[key] = val
  178. continue
  179. }
  180. item := hercules.Registry.Summon(key)[0].(hercules.ResultMergeablePipelineItem)
  181. mergedResult = item.MergeResults(mergedResult, val, mergedCommons, anotherCommons)
  182. mergedResults[key] = mergedResult
  183. }
  184. if mergedCommons.CommitsNumber == 0 {
  185. *mergedCommons = *anotherCommons
  186. } else {
  187. mergedCommons.Merge(anotherCommons)
  188. }
  189. }
  190. func getOptionsString() string {
  191. var leaves []string
  192. for _, leaf := range hercules.Registry.GetLeaves() {
  193. leaves = append(leaves, leaf.Name())
  194. }
  195. return strings.Join(leaves, ", ")
  196. }
  197. func init() {
  198. rootCmd.AddCommand(combineCmd)
  199. combineCmd.SetUsageFunc(combineCmd.UsageFunc())
  200. combineCmd.Flags().String("only", "", "Consider only the specified analysis. "+
  201. "Empty means all available. Choices: "+getOptionsString()+".")
  202. }