func run()

in Sources/CollectionsBenchmark/BenchmarkCLI/BenchmarkCLI+Results+Compare.swift [79:228]


    func run() throws {
      let first = try BenchmarkResults.load(from: self.first)
      let second = try BenchmarkResults.load(from: self.second)

      if chartLabels.count != 2 {
        throw Benchmark.Error("--chart-labels requires exactly two values")
      }
      if chartLabels[0] == chartLabels[1] {
        throw Benchmark.Error("--chart-labels requires two different values")
      }

      // Get list of all known tasks.
      let firstKnownTasks = first.alltaskIDs().filter { $0.label == firstLabel }
      let secondKnownTasks = second.alltaskIDs().filter { $0.label == secondLabel }
      let allKnownTasks = Set(
        firstKnownTasks.map { TaskID(title: $0.title) }
        + secondKnownTasks.map { TaskID(title: $0.title) }
      )

      let tasks = try self.tasks.resolve(
        allKnownTasks: allKnownTasks,
        ignoreLabels: true)

      let maxCharts = self.maxCharts ?? Int.max

      var missingFirst: [TaskID] = []
      var missingSecond: [TaskID] = []
      var missingData: [String] = []
      var common: [(score: Score, first: TaskResults, second: TaskResults)] = []
      for id in tasks {
        let beforeID = TaskID(label: firstLabel, title: id.title)
        let afterID = TaskID(label: secondLabel, title: id.title)
        let before = first[id: beforeID]
        guard before.sampleCount > 0 else {
          missingFirst.append(beforeID)
          continue
        }
        let after = second[id: afterID]
        guard after.sampleCount > 0 else {
          missingSecond.append(afterID)
          continue
        }
        guard let score = Score(before: before, after: after) else {
          missingData.append(beforeID.title)
          continue
        }
        common.append((score, before, after))
      }

      if !missingFirst.isEmpty {
        complain("Tasks missing from first file:")
        missingFirst.forEach { complain("  \($0)") }
      }
      if !missingSecond.isEmpty {
        complain("Tasks missing from second file:")
        missingSecond.forEach { complain("  \($0)") }
      }
      if !missingData.isEmpty {
        complain("Tasks with no overlapping measurements:")
        missingData.forEach { complain("  \($0)") }
      }

      guard !common.isEmpty else {
        throw Benchmark.Error("There is not enough data available to compare results")
      }

      let renderer = Graphics.bestAvailableRenderer
      let theme = try chartOptions.themeSpec.resolve(with: renderer)

      common.sort(by: { $0.score > $1.score })

      print("Tasks with difference scores larger than \(listCutoff):")
      print("  \(Score.header) Name")
      for item in common {
        guard item.score.score > listCutoff else { break }
        let mark = item.score.score > chartCutoff ? " (*)" : ""
        print("  \(item.score) \(item.first.taskID.title)\(mark)")
      }

      guard self.output != nil else { return }

      let (output, format, multifile) = try ImageFormat.resolve(
        stem: "diff",
        output: self.output,
        format: self.format,
        multifile: (self.multifile ? true
                        : self.singlefile ? false
                        : nil))

      // Generate charts.
      typealias Image = (title: String, score: Score, graphics: Graphics)
      var images: [Image] = []
      for (score, before, after) in common.prefix(maxCharts) {
        guard score.score > chartCutoff else { break }
        var results = BenchmarkResults()
        results.add(before.withLabel(chartLabels[0]))
        results.add(after.withLabel(chartLabels[1]))

        let chart = Chart(
          taskIDs: results.alltaskIDs(),
          in: results,
          options: try chartOptions.chartOptions())
        let graphics = chart.draw(
          bounds: chartOptions._bounds,
          theme: theme,
          renderer: renderer)
        images.append((before.taskID.title, score, graphics))
      }

      if multifile {
        guard output._isDirectory else {
          throw Benchmark.Error("Multifile output must be a directory: \(output)")
        }
        let dir = URL(output, isDirectory: true)
        for (i, (title, _, graphics)) in images.enumerated() {
          let data = try renderer.render(
            graphics,
            format: format.rawValue,
            bitmapScale: chartOptions.scale)
          let filename = self.filename(title: title, index: i, format: format)
          let url = dir.appendingPathComponent(filename)
          try data.write(to: url, options: .atomic)
        }
        print("\(images.count) image\(images.count == 1 ? "" : "s") generated in \(output).")
      } else {
        guard format.supportsSinglefileRendering else {
          throw Benchmark.Error("Format '\(format)' does not support multiple charts in a single file")
        }
        var doc = try renderer.documentRenderer(
          title: "Benchmark differentials",
          format: format,
          style: .flat)
        for (title, score, graphics) in images {
          try doc.item(
            title: "\(title) (score: \(score.typesetDescription))",
            graphics: graphics,
            collapsed: false)
        }
        for (score, a, _) in common.dropFirst(images.count) {
          guard score.score > listCutoff else { break }
          try doc.item(
            title: "\(a.taskID.title) (score: \(score.typesetDescription))",
            graphics: nil,
            collapsed: false)
        }
        let url = URL(output, isDirectory: false)
        try doc.render().write(to: url)
        print("\(images.count) images written to \(url.relativePath)")
      }
    }