Skip to content

Commit a841afd

Browse files
author
Dragos Manolescu
committed
Added Measurement; additional refactoring for warmup and reporting.
1 parent 1414549 commit a841afd

File tree

6 files changed

+202
-121
lines changed

6 files changed

+202
-121
lines changed

README.md

+12
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@ $ ./sbt
88
> run
99
```
1010

11+
There are three command-line arguments:
12+
13+
com.microWorkflow.jsonScalaPerftest.Main [-n num] [-w num] [-map]
14+
15+
-n is the number of iterations to run; the default value is 100, and the result is the mean value
16+
-w is the number of iterations for warm-up, i.e., executed prior to taking measurements; the default
17+
value is 5
18+
-map is optional and determines whether the measurements cover the
19+
object model mapping in addition to JSON parsing. Without this argument the measurements
20+
cover only the latter
21+
22+
1123
## Contact ##
1224

1325
- Dragos Manolescu
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright (c) 2012 Dragos Manolescu.
3+
*
4+
* Published under the Apache 2.0 license; see http://www.apache.org/licenses/LICENSE-2.0.html
5+
*/
6+
package com.microWorkflow.jsonScalaPerftest
7+
8+
import java.io.File
9+
import collection.immutable.HashMap
10+
11+
case class Category(directoryName: String, name: String) {
12+
13+
def this(fn: String) = {
14+
this(fn, fn.substring(fn.lastIndexOf('/') + 1))
15+
}
16+
17+
val datasets = Category.getFilesMatching(directoryName, fn => fn.isFile).map {
18+
each => new Dataset(each.getCanonicalPath)
19+
}
20+
21+
override def toString = {
22+
val sb = new StringBuilder
23+
sb.append(name)
24+
sb.append(datasets.map(ds => ds.toString).mkString(": ", ", ", ""))
25+
sb.toString()
26+
}
27+
28+
def measure(adaptor: LibraryAdaptor, doMap: Boolean, iterations: Int): HashMap[String, Measurement] = {
29+
datasets.foldLeft(new HashMap[String, Measurement])((m,d) => {
30+
adaptor.resetTimers()
31+
adaptor.measure(d, doMap, iterations)
32+
m + (d.name -> Measurement.takeMeasurement(adaptor.timerName))
33+
})
34+
}
35+
}
36+
37+
object Category {
38+
def getFilesMatching(path: String, p: File => Boolean) = {
39+
val dir = new java.io.File(path).getAbsolutePath
40+
new File(dir).listFiles.filter(file => p(file))
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright (c) 2012 Dragos Manolescu.
3+
*
4+
* Published under the Apache 2.0 license; see http://www.apache.org/licenses/LICENSE-2.0.html
5+
*/
6+
package com.microWorkflow.jsonScalaPerftest
7+
8+
import io.Source
9+
10+
case class Dataset(fileName: String, name: String) {
11+
12+
def this(fn: String) = {
13+
this(fn, fn.substring(fn.lastIndexOf('/') + 1, fn.lastIndexOf('.')))
14+
}
15+
16+
val docs: List[String] = Source.fromFile(fileName).getLines().toList
17+
18+
override def toString = {
19+
val sb = new StringBuilder
20+
sb.append(name)
21+
sb.append(" (")
22+
sb.append(docs.length)
23+
sb.append(" documents)")
24+
sb.toString()
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1+
/*
2+
* Copyright (c) 2012 Dragos Manolescu.
3+
*
4+
* Published under the Apache 2.0 license; see http://www.apache.org/licenses/LICENSE-2.0.html
5+
*/
16
package com.microWorkflow.jsonScalaPerftest
27

38
import com.yammer.metrics.Metrics
49
import java.util.concurrent.TimeUnit
510

6-
/**
7-
* Created with IntelliJ IDEA.
8-
* User: dam
9-
* Date: 12/2/12
10-
* Time: 7:58 PM
11-
* To change this template use File | Settings | File Templates.
12-
*/
1311
abstract class LibraryAdaptor(name: String) extends TimeMeasurements {
14-
lazy val initTimer = Metrics.newTimer(getClass, "%s-init".format(name), TimeUnit.MILLISECONDS, TimeUnit.MILLISECONDS)
15-
lazy val mainTimer = Metrics.newTimer(getClass, "%s-main".format(name), TimeUnit.MILLISECONDS, TimeUnit.MILLISECONDS)
12+
13+
val timerName = "%s-main".format(name)
14+
15+
lazy val mainTimer = Metrics.newTimer(getClass, timerName, TimeUnit.MILLISECONDS, TimeUnit.SECONDS)
1616

1717
def getName = name
1818

@@ -22,18 +22,15 @@ abstract class LibraryAdaptor(name: String) extends TimeMeasurements {
2222

2323
def runOnce(json: String, doMap:Boolean): Any
2424

25-
def measure(dataset: Dataset, doMap:Boolean) = {
26-
val context1 = initTimer.time()
27-
try {
28-
initialize()
29-
} finally {
30-
context1.stop()
31-
}
25+
def measure(dataset: Dataset, doMap:Boolean, iterations: Int) {
26+
initialize()
3227

3328
val context2 = mainTimer.time()
3429
try {
35-
for (doc <- dataset.docs)
36-
runOnce(doc, doMap)
30+
for (count <- 1 to iterations) {
31+
for (doc <- dataset.docs)
32+
runOnce(doc, doMap)
33+
}
3734
} catch {
3835
case ex:Exception => println("%s bombed while processing %s (%s)".format(name, dataset.name, ex.getMessage))
3936
} finally {
@@ -42,7 +39,6 @@ abstract class LibraryAdaptor(name: String) extends TimeMeasurements {
4239
}
4340

4441
def resetTimers() {
45-
initTimer.clear()
4642
mainTimer.clear()
4743
}
4844
}
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,13 @@
1+
/*
2+
* Copyright (c) 2012 Dragos Manolescu.
3+
*
4+
* Published under the Apache 2.0 license; see http://www.apache.org/licenses/LICENSE-2.0.html
5+
*/
16
package com.microWorkflow.jsonScalaPerftest
27

3-
import java.io.File
4-
import io.Source
5-
import com.yammer.metrics.reporting.CsvReporter
6-
import java.util.concurrent.TimeUnit
7-
import fr.janalyse.jmx.JMX
8-
import com.yammer.metrics.Metrics
8+
import collection.immutable.HashMap
99

10-
case class Dataset(fileName: String, name: String) {
11-
12-
def this(fn: String) = {
13-
this(fn, fn.substring(fn.lastIndexOf('/') + 1, fn.lastIndexOf('.')))
14-
}
15-
16-
val docs: List[String] = Source.fromFile(fileName).getLines().toList
17-
18-
override def toString = {
19-
val sb = new StringBuilder
20-
sb.append(name)
21-
sb.append(" (")
22-
sb.append(docs.length)
23-
sb.append(" documents)")
24-
sb.toString()
25-
}
26-
}
27-
28-
case class Category(directoryName: String, name: String) {
29-
30-
def this(fn: String) = {
31-
this(fn, fn.substring(fn.lastIndexOf('/') + 1))
32-
}
33-
34-
val datasets = Category.getFilesMatching(directoryName, fn => fn.isFile).map {
35-
each => new Dataset(each.getCanonicalPath)
36-
}
37-
38-
override def toString = {
39-
val sb = new StringBuilder
40-
sb.append(name)
41-
sb.append(datasets.map(ds => ds.toString).mkString(": ", ", ", ""))
42-
sb.toString()
43-
}
44-
45-
def measure(adapter: LibraryAdaptor, doMap: Boolean) {
46-
for (dataset <- datasets) {
47-
adapter.measure(dataset, doMap)
48-
}
49-
}
50-
}
51-
52-
object Category {
53-
def getFilesMatching(path: String, p: File => Boolean) = {
54-
val dir = new java.io.File(path).getAbsolutePath
55-
new File(dir).listFiles.filter(file => p(file))
56-
}
57-
}
58-
59-
60-
case class Experiment(measurementsPath: String) {
10+
case class Experiment(warmUpIterations: Int=5) {
6111

6212
val adapters = Array(new liftjson.LiftJsonAdaptor("lift")
6313
, new jerkson.JerksonAdaptor("jerkson")
@@ -70,62 +20,65 @@ case class Experiment(measurementsPath: String) {
7020
, new jackson.JacksonAdaptor("jackson")
7121
)
7222

73-
val loopCounter = Metrics.newCounter(this.getClass, "main loop")
74-
75-
val categories = Category.getFilesMatching("data", f => f.isDirectory).map(d => new Category(d.getCanonicalPath))
23+
val categories = Category
24+
.getFilesMatching("data", f => f.isDirectory)
25+
.map(d => new Category(d.getCanonicalPath))
7626

77-
def run(iterations: Int, doMap: Boolean) = {
78-
loopCounter.clear()
79-
val oldFiles = Category.getFilesMatching(measurementsPath, f => f.isFile)
80-
println("Removing %d old files".format(oldFiles.length))
81-
if (oldFiles != null)
82-
oldFiles.foreach(f => f.delete())
83-
CsvReporter.enable(new File(measurementsPath), 100, TimeUnit.MILLISECONDS)
27+
def run(iterations: Int, doMap: Boolean): Array[(String, HashMap[String, Measurement])] = {
8428
val adaptersToTest = if (doMap) adapters.filter(_.hasMap) else adapters
85-
for (count <- 1 to iterations) {
86-
categories.flatMap(c => (adaptersToTest.map {
87-
each => c.measure(each, doMap)
88-
}))
89-
loopCounter.inc()
90-
}
29+
categories.flatMap(c => (adaptersToTest.map {
30+
each => (c.name -> c.measure(each, doMap, iterations))
31+
}))
9132
}
9233

93-
def takeMeasurements(iterations: Int, doMap: Boolean) {
94-
val where = new File(measurementsPath)
95-
where.mkdirs()
96-
print("Priming caches...")
97-
run(iterations, doMap)
98-
print("Measuring timings...")
34+
def takeMeasurements(iterations: Int, doMap: Boolean): Array[(String, HashMap[String, Measurement])] = {
35+
println("Warming up (%d iterations)...".format(warmUpIterations))
36+
run(warmUpIterations, doMap)
37+
println("Measuring (%d iterations)...".format(iterations))
9938
run(iterations, doMap)
10039
}
101-
102-
def printMeanValues() {
103-
JMX.once() {
104-
jmx => {
105-
val beans = jmx.mbeans().filter(b => b.name.contains("microWorkflow".toCharArray))
106-
for (bean <- beans) {
107-
val mean = bean.getDouble("Mean") match {
108-
case Some(d) => d
109-
case None => 0.0
110-
}
111-
println("%s: %fms".format(bean.name.substring(bean.name.indexOf('=') + 1), mean))
112-
}
113-
}
114-
}
115-
}
11640
}
11741

11842

11943
object Main {
44+
val usage =
45+
"""
46+
|Usage: com.microWorkflow.jsonScalaPerftest.Main [-n num] [-w num] [-map]
47+
""".stripMargin
12048

12149
def main(args: Array[String]) {
122-
val where = if (args.length>0) args(0) else "/tmp/measurements"
123-
val iterations:Int = if (args.length>1) args(1).toInt else 100
124-
val doMap = if (args.length>2) args(2).equalsIgnoreCase("-map") else false
125-
println("Runing %d iterations %s object mapping; test results in %s"
126-
.format(iterations, if (doMap) "with" else "without", where))
127-
val e = Experiment(where)
128-
e.takeMeasurements(iterations, doMap)
129-
println("Done")
50+
if (args.isEmpty) println(usage)
51+
else {
52+
53+
def nextOption(map: Map[Symbol, Any], list: List[String]): Map[Symbol, Any] = {
54+
list match {
55+
case Nil => map
56+
case "-n" :: value :: tail => nextOption(map + ('iterations -> value.toInt), tail)
57+
case "-w" :: value :: tail => nextOption(map + ('warmUp -> value.toInt), tail)
58+
case "-map" :: tail => nextOption(map + ('map -> true), tail)
59+
case _ :: tail =>
60+
println("Don't know what to do with '%s'%s".format(list.head, usage))
61+
sys.exit(1)
62+
}
63+
}
64+
val options = nextOption(new HashMap[Symbol, Any](), args.toList)
65+
66+
val iterations = options.getOrElse('iterations, 100).asInstanceOf[Int]
67+
val doMap = options.getOrElse('map, false).asInstanceOf[Boolean]
68+
val warmUpIterations = options.getOrElse('warmUp, 5).asInstanceOf[Int]
69+
70+
println("Runing %d iterations %s object mapping"
71+
.format(iterations, if (doMap) "with" else "without"))
72+
val experiment = Experiment(warmUpIterations)
73+
val ms = experiment.takeMeasurements(iterations, doMap)
74+
for (m <- ms) {
75+
print("Category: %s\n".format(m._1))
76+
for (d <- m._2.iterator)
77+
print("\t dataset '%s', measurement: %s\n".format(d._1, d._2))
78+
}
79+
80+
sys.exit()
81+
}
82+
13083
}
13184
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright (c) 2012 Dragos Manolescu.
3+
*
4+
* Published under the Apache 2.0 license; see http://www.apache.org/licenses/LICENSE-2.0.html
5+
*/
6+
package com.microWorkflow.jsonScalaPerftest
7+
8+
import fr.janalyse.jmx.{RichMBean, JMX}
9+
10+
case class Measurement(name: String, value: Double, u: String="") {
11+
12+
val unit = if (u.isEmpty) None else Some(u)
13+
14+
override def toString = {
15+
val sb = new StringBuilder
16+
sb.append(name)
17+
sb.append(" = ")
18+
sb.append(value)
19+
sb.append(" ")
20+
sb.append(unit match {
21+
case Some(s) => s
22+
case None => "(adimensional)"
23+
})
24+
sb.toString()
25+
}
26+
}
27+
28+
object Measurement {
29+
30+
def takeMeasurement(name: String) = {
31+
Measurement(name, getValuesFor(name, "Mean"), "ms")
32+
}
33+
34+
def getDoubleFrom(bean: RichMBean, key: String) = {
35+
bean.getDouble(key) match {
36+
case Some(d) => d
37+
case None => 0.0
38+
}
39+
}
40+
41+
def getValuesFor(nameKey: String, valueKey: String): Double = {
42+
JMX.once() {
43+
jmx => {
44+
jmx.mbeans()
45+
.filter(b => b.name.contains(nameKey.toCharArray))
46+
.map(bean => getDoubleFrom(bean, valueKey))
47+
.head
48+
}
49+
}
50+
}
51+
}
52+

0 commit comments

Comments
 (0)