JMHProcessor.kt 6.6 KB
Newer Older
1
2
3
4
package se.lth.cs

import org.apache.commons.csv.CSVFormat
import org.apache.commons.csv.CSVParser
5
import org.apache.commons.csv.CSVPrinter
6
import org.apache.commons.csv.CSVRecord
7
import se.lth.cs.bcgen.BCBenchmarkPackage
8
import se.lth.cs.bcgen.MethodSelectionType
9
10
11
12
import java.io.File
import java.io.FileReader
import java.io.Reader
import java.io.Writer
13

14
15
16
/**
 * A Class for processing JMH benchmark data
 */
17
class JMHProcessor {
18

19
    fun process(file: File): List<JMHRecord> {
Noric Couderc's avatar
Noric Couderc committed
20
        return process(FileReader(file))
21
22
    }

23
    data class JMHRecord(val seed : Long,
24
25
26
27
28
29
30
                         val size : Int,
                         val baseStructureSize : Int,
                         val collection : String,
                         val datastructure : String,
                         val best : String,
                         val methodSelection : String? = null) {

Noric Couderc's avatar
Noric Couderc committed
31
        fun toList() : List<String> {
Noric Couderc's avatar
Noric Couderc committed
32
33
            return listOf(collection, seed.toString(), size.toString(),
                    baseStructureSize.toString(), datastructure, best)
Noric Couderc's avatar
Noric Couderc committed
34
        }
35

36
37
        fun generateSyntheticBenchmark() : BCBenchmarkPackage<*>? {
            var syntheticBenchmark : BCBenchmarkPackage<*>? = null
38
            val dataStructure = getClassFromSimpleName(datastructure)
39

40
            if (collection == "List") {
41
42
43
                syntheticBenchmark = BCBenchmarkPackage.LIST(seed, size, baseStructureSize,
                        MethodSelectionType.fromString(methodSelection),
                        dataStructure as MutableList<Object>?)
44
                return syntheticBenchmark
45
46
47
            }

            if (collection == "Map") {
48
49
50
                syntheticBenchmark = BCBenchmarkPackage.MAP(seed, size, baseStructureSize,
                        MethodSelectionType.fromString(methodSelection),
                        dataStructure as MutableMap<Any, Any>?)
51
                return syntheticBenchmark
52
53
54
            }

            if (collection == "Set") {
55
56
57
                syntheticBenchmark = BCBenchmarkPackage.SET(seed, size, baseStructureSize,
                        MethodSelectionType.fromString(methodSelection),
                        dataStructure as MutableSet<Object>?)
58
                return syntheticBenchmark
59
60
61
62
63
64
65
66
            }
            return null
        }

        private fun getClassFromSimpleName(name : String) : Any {
            val className = "java.util.$name"
            return Class.forName(className).getConstructor().newInstance()
        }
Noric Couderc's avatar
Noric Couderc committed
67
    }
68

69
70
71
72
73
74
75
    private val selectedColumns = listOf(
            "Benchmark",
            "Param: seed",
            "Param: applicationSize",
            "Param: baseStructureSize"
    )

76
77
78
    /**
     * Reads CSV data from a Reader and produces a list of JMH Record data
     */
79
    fun process(reader : Reader): List<JMHRecord> {
80
        var parser = CSVParser(reader, CSVFormat.DEFAULT.withFirstRecordAsHeader())
Noric Couderc's avatar
Noric Couderc committed
81
        // We are grouping the parameters by any parameter except the data structure name (which we want)
82

83
        // We group the records by our selected columns
84
85
86
87
        val seedsToRecords = parser.records.groupBy { record ->
            selectedColumns.map { column -> record.get(column) }
        }

88
        return seedsToRecords.values.flatMap { records ->
Noric Couderc's avatar
Noric Couderc committed
89
            val interfaceName = records[0].get("Benchmark").let { processBenchmarkName(it) }
90
            val seed = records[0].get("Param: seed")
91
                    .let { it.toLong()}
92
            val size = records[0].get("Param: applicationSize")
93
                    .let { Integer.parseInt(it)}
94
95
            val baseStructureSize = records[0].get("Param: baseStructureSize")
                    .let { Integer.parseInt(it)}
96

97
            val best = getBestDataStructure(records)
98

99
            records.map{
100
101
102
103
104
105
                val methodSelection =
                        if (it.isSet("Param: methodSelectionStrategyId")) {
                            it.get("Param: methodSelectionStrategyId")
                        } else {
                            null
                        }
106
                JMHRecord(seed, size, baseStructureSize,
Noric Couderc's avatar
Noric Couderc committed
107
108
109
                    interfaceName.intern(),
                    it.get("Param: datastructureName").intern(),
                    best!!.intern(),
110
                    methodSelection)
111
            }
112
113
        }
    }
114

115
116
117
118
119
120
121
    /**
     * Get the "worst" running time for a JMH benchmark record
     */
    private fun getWorstScore(record : CSVRecord): Double {
        return record.get("Score").toDouble() - record.get("Score Error (99.9%)").toDouble()
    }

122
123
124
125
126
127
    private fun getBestDataStructure(records : List<CSVRecord>): String? {
        // Precondition
        // All records must have
        // - same seed
        // - same benchmark (List, Map, etc)
        // - same application size
128
        // - same base structure size
129
130
131
132
        assert(records.all { record ->
            selectedColumns.map{ record.get(it)} ==
            selectedColumns.map{ records[0].get(it)}})

Noric Couderc's avatar
Noric Couderc committed
133
134
        val maxScore = records.maxBy { getWorstScore(it) }
        return maxScore!!.get("Param: datastructureName")
135
136
    }

Noric Couderc's avatar
Noric Couderc committed
137
    class JMHProcessorException(override val message: String?) : Exception(message)
138
139
140
141
142
143
144
145
146

    fun processBenchmarkName(benchmark : String) : String {
        val options = listOf("List", "Map", "Set")
        val name = benchmark.findAnyOf(options)?.second
        if (name.isNullOrBlank()) {
            throw JMHProcessorException("Benchmark name does not contain any of $options")
        }
        return name
    }
147
148
149
150

    /**
     * Prints the given records to a file
     */
151
    fun print(writer : Writer, records : List<JMHRecord>) {
152
        val printer = CSVPrinter(writer, CSVFormat.DEFAULT.withFirstRecordAsHeader())
153
        printer.printRecord("Interface", "Seed", "Size", "BaseStructureSize", "Best")
154
        for (record in records) {
Noric Couderc's avatar
Noric Couderc committed
155
            printer.printRecord(record.toList())
156
157
158
        }
        writer.close()
    }
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182

    companion object {
        /**
         * Static method giving the expected CSV header
         * @return A string for the expected CSV header
         */
        fun getExpectedHeader() : String {
            val header =
                    listOf(
                            "Benchmark",
                            "Mode",
                            "Threads",
                            "Samples",
                            "Score",
                            "Score Error (99.9%)",
                            "Unit",
                            "Param: applicationSize",
                            "Param: baseStructureSize",
                            "Param: datastructureName",
                            "Param: seed"
                    ).map { "\"$it\""}.joinToString(",")
            return header
        }
    }
183
}