diff --git a/Plugins/BenchmarkTool/BenchmarkTool+Export+InfluxCSVFormatter.swift b/Plugins/BenchmarkTool/BenchmarkTool+Export+InfluxCSVFormatter.swift index 8e8ff8a8..a03d2d88 100644 --- a/Plugins/BenchmarkTool/BenchmarkTool+Export+InfluxCSVFormatter.swift +++ b/Plugins/BenchmarkTool/BenchmarkTool+Export+InfluxCSVFormatter.swift @@ -19,9 +19,16 @@ struct ExportableBenchmark: Codable { struct TestData: Codable { var test: String + var tags: [String: String] + var fields: [String: Field] var iterations: Int var warmupIterations: Int var data: [TestMetricData] + + struct Field: Codable { + let type: String + let value: String + } } struct TestMetricData: Codable { @@ -53,18 +60,30 @@ class InfluxCSVFormatter { let processors = machine.processors let memory = machine.memory - if header { - let dataTypeHeader = "#datatype tag,tag,tag,tag,tag,tag,tag,tag,tag,double,double,long,long,dateTime\n" - finalFileFormat.append(dataTypeHeader) - let headers = "measurement,hostName,processoryType,processors,memory,kernelVersion,metric,unit,test,value,test_average,iterations,warmup_iterations,time\n" - finalFileFormat.append(headers) - } - for testData in exportableBenchmark.benchmarks { + let orderedTags = testData.tags.map({ (key: $0, value: $1) }) + let orderedFields = testData.fields.map({ (key: $0, field: $1) }) + + let customHeaderDataTypes = String(repeating: "tag,", count: orderedTags.count) + + orderedFields.map({ "\($0.field.type)," }).joined() + + let customHeaders = (orderedTags.map({ "\($0.key)," }) + + orderedFields.map({ "\($0.key)," })).joined() + + if header { + let dataTypeHeader = "#datatype tag,tag,tag,tag,tag,tag,tag,tag,tag,\(customHeaderDataTypes)double,double,long,long,dateTime\n" + finalFileFormat.append(dataTypeHeader) + let headers = "measurement,hostName,processoryType,processors,memory,kernelVersion,metric,unit,test,\(customHeaders)value,test_average,iterations,warmup_iterations,time\n" + finalFileFormat.append(headers) + } + let testName = testData.test let iterations = testData.iterations let warmup_iterations = testData.warmupIterations + let customTagValues = orderedTags.map({ "\($0.value)," }).joined() + let customFieldValues = orderedFields.map({ "\($0.field.value)," }).joined() + for granularData in testData.data { let metric = granularData.metric .replacingOccurrences(of: " ", with: "") @@ -73,10 +92,11 @@ class InfluxCSVFormatter { for dataTableValue in granularData.metricsdata { let time = ISO8601DateFormatter().string(from: Date()) - let dataLine = "\(exportableBenchmark.target),\(hostName),\(processorType),\(processors),\(memory),\(kernelVersion),\(metric),\(units),\(testName),\(dataTableValue),\(average),\(iterations),\(warmup_iterations),\(time)\n" + let dataLine = "\(exportableBenchmark.target),\(hostName),\(processorType),\(processors),\(memory),\(kernelVersion),\(metric),\(units),\(testName),\(customTagValues)\(customFieldValues)\(dataTableValue),\(average),\(iterations),\(warmup_iterations),\(time)\n" finalFileFormat.append(dataLine) } } + finalFileFormat.append("\n") } return finalFileFormat @@ -161,9 +181,23 @@ extension BenchmarkTool { iterations = results.statistics.measurementCount warmupIterations = results.warmupIterations } + + let exportConfig = profile.benchmark.configuration.exportConfigurations?[.influx] as? InfluxExportConfiguration + + var tags: [String: String] = [:] + var fields: [String: TestData.Field] = [:] + for (tag, value) in profile.benchmark.configuration.tags { + if let field = exportConfig?.fields[tag] { + fields[tag] = TestData.Field(type: field.rawValue, value: value) + } else { + tags[tag] = value + } + } testList.append( TestData(test: cleanedTestName, + tags: tags, + fields: fields, iterations: iterations, warmupIterations: warmupIterations, data: benchmarkResultData) diff --git a/Sources/Benchmark/BenchmarkExportConfigurations/BenchmarkExportConfiguration.swift b/Sources/Benchmark/BenchmarkExportConfigurations/BenchmarkExportConfiguration.swift index 7038ca9e..880bd272 100644 --- a/Sources/Benchmark/BenchmarkExportConfigurations/BenchmarkExportConfiguration.swift +++ b/Sources/Benchmark/BenchmarkExportConfigurations/BenchmarkExportConfiguration.swift @@ -5,6 +5,10 @@ public struct BenchmarkExportConfigurationKey: Hashable, Codable { private let value: String } +public extension BenchmarkExportConfigurationKey { + static var influx: Self { .init(value: #function) } +} + /// The set of export configurations for a particular benchmark public struct BenchmarkExportConfigurations: Codable { let configs: [BenchmarkExportConfigurationKey: any BenchmarkExportConfiguration] @@ -38,6 +42,7 @@ extension BenchmarkExportConfigurationKey { static func resolveConfigType(from key: Self) -> BenchmarkExportConfiguration.Type? { switch key { // Add a case here when adding a new exporter config + case .influx: InfluxExportConfiguration.self default: nil } } diff --git a/Sources/Benchmark/BenchmarkExportConfigurations/InfluxExportConfiguration.swift b/Sources/Benchmark/BenchmarkExportConfigurations/InfluxExportConfiguration.swift new file mode 100644 index 00000000..25505f4a --- /dev/null +++ b/Sources/Benchmark/BenchmarkExportConfigurations/InfluxExportConfiguration.swift @@ -0,0 +1,29 @@ +public struct InfluxExportConfiguration: BenchmarkExportConfiguration { + /// The set of benchmark tags to interpret as Influx fields. + /// The default is to treat benchmark tags as Influx tags. + public let fields: [String: InfluxDataType] + + public enum InfluxDataType: String, Codable { + // References: https://docs.influxdata.com/influxdb/cloud/reference/syntax/annotated-csv/#data-types + + case boolean + /// Unsigned 64-bit integer + case unsignedLong + /// Signed 64-bit integer + case long + /// IEEE-754 64-bit floating-point number + case double + /// UTF-8 encoded string + case string + /// Base64 encoded sequence of bytes as defined in RFC 4648 + case base64Binary + /// Instant in time, may be followed with a colon : and a description of the format (number, RFC3339, RFC3339Nano) + case dateTime + /// Length of time represented as an unsigned 64-bit integer number of nanoseconds + case duration + } + + public init(fields: [String: InfluxDataType]) { + self.fields = fields + } +}