Skip to content

Commit d818e71

Browse files
committed
Merge branch 'master' into A53
2 parents 4a244e2 + 47fabe6 commit d818e71

File tree

23 files changed

+762
-15
lines changed

23 files changed

+762
-15
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,25 @@ The Public API will run on `$(docker-machine ip):8082`
225225
By default, the `HDMA Platform` runs with a log level of `INFO`. This can be changed by establishing a different log level in the `HMDA_LOGLEVEL` environment variable.
226226
For the different logging options, see the [reference.conf](https://github.com/akka/akka/blob/master/akka-actor/src/main/resources/reference.conf#L38) default configuration file for `Akka`.
227227

228+
#### API Load Testing
229+
230+
A load testing scenario for the API has been built using [Gatling](https://gatling.io/). The parameters for this test can be configured in the `cluster/src/test/resources/application.conf` file.
231+
To run the default scenario:
232+
233+
```shell
234+
$ sbt
235+
> project cluster
236+
> gatling:test
237+
```
238+
239+
After the test is run, some statistics will be presented on the screen. To further study the results of the test, a report can be opened on a browser from the sbt prompt:
240+
241+
```shell
242+
> gatling:lastReport
243+
```
244+
245+
** NOTE: The load test requires the sample panel [file](loader/src/main/resources/inst_data_2017_dummy.csv) to be loaded before running it.
246+
228247
#### To run the entire platform
229248

230249
1. Ensure you have a Docker Machine with sufficient resources, as described in the [Docker](#docker) section above.

build.sbt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import Dependencies._
33
import sbtassembly.AssemblyPlugin.autoImport._
44
import spray.revolver.RevolverPlugin.autoImport.Revolver
55
import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._
6+
import io.gatling.sbt.GatlingPlugin
67

7-
val commonDeps = Seq(logback, scalaTest, scalaCheck)
8+
val commonDeps = Seq(logback, scalaTest, scalaCheck, gatling, gatlingHighcharts)
89

910
val akkaDeps = commonDeps ++ Seq(akka, akkaCluster, akkaClusterTools, akkaSlf4J, akkaStream, akkaTestkit, constructr, constructrZookeeper, akkaClusterManagement)
1011

@@ -58,14 +59,15 @@ lazy val hmda = (project in file("."))
5859

5960

6061
lazy val cluster = (project in file("cluster"))
62+
.enablePlugins(GatlingPlugin)
6163
.settings(hmdaBuildSettings: _*)
6264
.settings(
6365
Seq(
6466
libraryDependencies ++= akkaDeps ++ configDeps
6567
)
6668
)
6769
.dependsOn(
68-
modelJVM,
70+
modelJVM % "compile->compile;test->test",
6971
parserJVM,
7072
apiModel,
7173
api,

census/src/main/resources/jul_2015_cbsa.csv

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

census/src/main/scala/hmda/census/model/CbsaLookup.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import com.github.tototoshi.csv.CSVParser.parse
1010
object CbsaLookup extends CbsaResourceUtils {
1111
val values: Seq[Cbsa] = {
1212
val lines = resourceLines("/jul_2015_cbsa.csv", "ISO-8859-1")
13-
lines.map { line =>
13+
lines.drop(3).map { line =>
1414
val values = parse(line, '\\', ',', '"').getOrElse(List())
1515
val cbsaCode = values(0)
1616
val metroDivCode = values(1)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
hmda {
2+
benchmark {
3+
host = "localhost"
4+
host = ${?HMDA_BENCHMARK_HOST}
5+
port = 8080
6+
port = ${?HMDA_BENCHMARK_PORT}
7+
adminPort = 8081
8+
adminPort = ${?HMDA_BENCHMARK_ADMIN_PORT}
9+
nrOfUsers = 50
10+
rampUpTime = 30
11+
feederFile = "institutions100.csv"
12+
}
13+
}

cluster/src/test/resources/clean_164-lars.txt

Lines changed: 165 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
institutionId,fileName
2+
74140,clean_164-lars.txt
3+
24949,clean_164-lars.txt
4+
212465,clean_164-lars.txt
5+
288853,clean_164-lars.txt
6+
18827,clean_164-lars.txt
7+
23456,clean_164-lars.txt
8+
35570,clean_164-lars.txt
9+
886,clean_164-lars.txt
10+
60143,clean_164-lars.txt
11+
4277,clean_164-lars.txt
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
institutionId,fileName
2+
74140,clean_164-lars.txt
3+
24949,clean_164-lars.txt
4+
212465,clean_164-lars.txt
5+
288853,clean_164-lars.txt
6+
18827,clean_164-lars.txt
7+
23456,clean_164-lars.txt
8+
35570,clean_164-lars.txt
9+
886,clean_164-lars.txt
10+
60143,clean_164-lars.txt
11+
3458,clean_164-lars.txt
12+
792,clean_164-lars.txt
13+
27614,clean_164-lars.txt
14+
11640,clean_164-lars.txt
15+
16551,clean_164-lars.txt
16+
2277093,clean_164-lars.txt
17+
2027364,clean_164-lars.txt
18+
5210,clean_164-lars.txt
19+
2318482,clean_164-lars.txt
20+
2282,clean_164-lars.txt
21+
13082,clean_164-lars.txt
22+
77422,clean_164-lars.txt
23+
208244,clean_164-lars.txt
24+
2350222,clean_164-lars.txt
25+
457,clean_164-lars.txt
26+
13103,clean_164-lars.txt
27+
12281,clean_164-lars.txt
28+
63573,clean_164-lars.txt
29+
339858,clean_164-lars.txt
30+
34742,clean_164-lars.txt
31+
143662,clean_164-lars.txt
32+
1864722,clean_164-lars.txt
33+
2161,clean_164-lars.txt
34+
4192,clean_164-lars.txt
35+
27070,clean_164-lars.txt
36+
17259,clean_164-lars.txt
37+
24453,clean_164-lars.txt
38+
18489,clean_164-lars.txt
39+
16748,clean_164-lars.txt
40+
4231,clean_164-lars.txt
41+
14874,clean_164-lars.txt
42+
14865,clean_164-lars.txt
43+
1072246,clean_164-lars.txt
44+
7979,clean_164-lars.txt
45+
26176,clean_164-lars.txt
46+
370271,clean_164-lars.txt
47+
27847,clean_164-lars.txt
48+
7287,clean_164-lars.txt
49+
10849,clean_164-lars.txt
50+
11387,clean_164-lars.txt
51+
3720,clean_164-lars.txt
52+
23504,clean_164-lars.txt
53+
9357,clean_164-lars.txt
54+
2351078,clean_164-lars.txt
55+
64150,clean_164-lars.txt
56+
4839,clean_164-lars.txt
57+
2399940,clean_164-lars.txt
58+
6329,clean_164-lars.txt
59+
31255,clean_164-lars.txt
60+
379920,clean_164-lars.txt
61+
1223440,clean_164-lars.txt
62+
17147,clean_164-lars.txt
63+
229801,clean_164-lars.txt
64+
58243,clean_164-lars.txt
65+
75633,clean_164-lars.txt
66+
48374,clean_164-lars.txt
67+
2376,clean_164-lars.txt
68+
26242,clean_164-lars.txt
69+
9807,clean_164-lars.txt
70+
85052,clean_164-lars.txt
71+
2554,clean_164-lars.txt
72+
18788,clean_164-lars.txt
73+
249612,clean_164-lars.txt
74+
1655797,clean_164-lars.txt
75+
354,clean_164-lars.txt
76+
12030,clean_164-lars.txt
77+
1829,clean_164-lars.txt
78+
1578449,clean_164-lars.txt
79+
1074736,clean_164-lars.txt
80+
266271,clean_164-lars.txt
81+
13390,clean_164-lars.txt
82+
17473,clean_164-lars.txt
83+
33147,clean_164-lars.txt
84+
29878,clean_164-lars.txt
85+
233031,clean_164-lars.txt
86+
41245,clean_164-lars.txt
87+
1454,clean_164-lars.txt
88+
2213046,clean_164-lars.txt
89+
12937,clean_164-lars.txt
90+
28255,clean_164-lars.txt
91+
17464,clean_164-lars.txt
92+
214807,clean_164-lars.txt
93+
26765,clean_164-lars.txt
94+
2318437,clean_164-lars.txt
95+
3588,clean_164-lars.txt
96+
242,clean_164-lars.txt
97+
28013,clean_164-lars.txt
98+
18854,clean_164-lars.txt
99+
2198,clean_164-lars.txt
100+
66154,clean_164-lars.txt
101+
41629,clean_164-lars.txt
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package api.http.benchmark
2+
3+
import com.typesafe.config.ConfigFactory
4+
import io.gatling.core.Predef._
5+
import io.gatling.http.Predef._
6+
import scala.concurrent.duration._
7+
import scala.language.postfixOps
8+
9+
class FilingSimulation extends Simulation {
10+
11+
val config = ConfigFactory.load()
12+
val host = config.getString("hmda.benchmark.host")
13+
val port = config.getInt("hmda.benchmark.port")
14+
val adminPort = config.getInt("hmda.benchmark.adminPort")
15+
val nrOfUsers = config.getInt("hmda.benchmark.nrOfUsers")
16+
val rampUpTime = config.getInt("hmda.benchmark.rampUpTime")
17+
val feeder = csv(config.getString("hmda.benchmark.feederFile")).random
18+
19+
val httpProtocol = http
20+
.baseURL(s"http://$host:$port")
21+
.acceptHeader("text/html,application/xhtml+xml,application/json;q=0.9,*/*;q=0.8")
22+
.acceptEncodingHeader("gzip, deflate")
23+
.acceptLanguageHeader("en-US,en;q=0.5")
24+
.acceptLanguageHeader("en-US,en;q=0.5")
25+
.userAgentHeader("Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0")
26+
.header("cfpb-hmda-username", "user")
27+
.disableCaching
28+
29+
object Institutions {
30+
31+
val hmdaFilingApi =
32+
feed(feeder)
33+
.exec(http("Search Institutions")
34+
.get("/institutions")
35+
.header("cfpb-hmda-institutions", "${institutionId}")
36+
.check(status is 200))
37+
.pause(1)
38+
.exec(http("Institution by id")
39+
.get("/institutions/${institutionId}")
40+
.header("cfpb-hmda-institutions", "${institutionId}")
41+
.check(status is 200)
42+
.check(jsonPath("$.institution.id") is "${institutionId}"))
43+
.pause(1)
44+
.exec(http("List Filings")
45+
.get("/institutions/${institutionId}/filings/2017")
46+
.header("cfpb-hmda-institutions", "${institutionId}")
47+
.check(status is 200))
48+
.pause(1)
49+
.exec(http("Create Submission")
50+
.post("/institutions/${institutionId}/filings/2017/submissions")
51+
.header("cfpb-hmda-institutions", "${institutionId}")
52+
.check(status is 201)
53+
.check(jsonPath("$.id.sequenceNumber").saveAs("submissionId")))
54+
.pause(2)
55+
.exec(http("Upload file")
56+
.post("/institutions/${institutionId}/filings/2017/submissions/${submissionId}")
57+
.header("cfpb-hmda-institutions", "${institutionId}")
58+
.bodyPart(RawFileBodyPart("file", "${fileName}"))
59+
.check(status is 202))
60+
.pause(10)
61+
.exec(http("View edits")
62+
.get("/institutions/${institutionId}/filings/2017/submissions/${submissionId}/edits")
63+
.header("cfpb-hmda-institutions", "${institutionId}")
64+
.check(status is 200)
65+
.check(jsonPath("$.status.code") is "8"))
66+
.pause(5)
67+
.exec(http("Validate Quality Edits")
68+
.post("/institutions/${institutionId}/filings/2017/submissions/${submissionId}/edits/quality")
69+
.header("cfpb-hmda-institutions", "${institutionId}")
70+
.body(StringBody("""{ "verified": true }""")).asJSON
71+
.check(status is 200))
72+
.pause(1)
73+
.exec(http("Validate Macro Edits")
74+
.post("/institutions/${institutionId}/filings/2017/submissions/${submissionId}/edits/macro")
75+
.header("cfpb-hmda-institutions", "${institutionId}")
76+
.body(StringBody("""{ "verified": true }""")).asJSON
77+
.check(status is 200))
78+
.pause(1)
79+
.exec(http("Get Summary")
80+
.get("/institutions/${institutionId}/filings/2017/submissions/${submissionId}/summary")
81+
.header("cfpb-hmda-institutions", "${institutionId}")
82+
.check(status is 200))
83+
.pause(1)
84+
.exec(http("IRS Report")
85+
.get("/institutions/${institutionId}/filings/2017/submissions/${submissionId}/irs")
86+
.header("cfpb-hmda-institutions", "${institutionId}")
87+
.check(status is 200))
88+
.pause(1)
89+
.exec(http("Sign Submission")
90+
.post("/institutions/${institutionId}/filings/2017/submissions/${submissionId}/sign")
91+
.header("cfpb-hmda-institutions", "${institutionId}")
92+
.body(StringBody(""" { "signed": true } """)).asJSON
93+
.check(status is 200)
94+
.check(jsonPath("$.status.code") is "10"))
95+
96+
}
97+
98+
val user = scenario("HMDA User")
99+
.exec(Institutions.hmdaFilingApi)
100+
101+
setUp(
102+
user.inject(rampUsers(nrOfUsers) over (rampUpTime seconds)).protocols(httpProtocol)
103+
)
104+
105+
}

model/shared/src/main/scala/hmda/model/publication/reports/BorrowerCharacteristic.scala

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,26 @@ package hmda.model.publication.reports
22

33
sealed trait BorrowerCharacteristic
44

5-
case class RaceBorrowerCharacteristic(races: List[RaceCharacteristic]) extends BorrowerCharacteristic
5+
case class RaceBorrowerCharacteristic(races: List[RaceCharacteristic]) extends BorrowerCharacteristic {
6+
def +(rbc: RaceBorrowerCharacteristic) = {
7+
val combined = races.map(r =>
8+
r + rbc.races.find(_.race == r.race).get)
9+
RaceBorrowerCharacteristic(combined)
10+
}
11+
}
612

7-
case class EthnicityBorrowerCharacteristic(ethnicities: List[EthnicityCharacteristic]) extends BorrowerCharacteristic
13+
case class EthnicityBorrowerCharacteristic(ethnicities: List[EthnicityCharacteristic]) extends BorrowerCharacteristic {
14+
def +(ebc: EthnicityBorrowerCharacteristic) = {
15+
val combined = ethnicities.map(e =>
16+
e + ebc.ethnicities.find(_.ethnicity == e.ethnicity).get)
17+
EthnicityBorrowerCharacteristic(combined)
18+
}
19+
}
820

9-
case class MinorityStatusBorrowerCharacteristic(minoritystatus: List[MinorityStatusCharacteristic]) extends BorrowerCharacteristic
21+
case class MinorityStatusBorrowerCharacteristic(minoritystatus: List[MinorityStatusCharacteristic]) extends BorrowerCharacteristic {
22+
def +(msbc: MinorityStatusBorrowerCharacteristic) = {
23+
val combined = minoritystatus.map(m =>
24+
m + msbc.minoritystatus.find(_.minorityStatus == m.minorityStatus).get)
25+
MinorityStatusBorrowerCharacteristic(combined)
26+
}
27+
}
Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,29 @@
11
package hmda.model.publication.reports
22

3-
sealed trait Characteristic
3+
sealed trait Characteristic {
4+
def dispositions: List[Disposition]
5+
}
46

5-
case class RaceCharacteristic(race: RaceEnum, dispositions: List[Disposition]) extends Characteristic
7+
case class RaceCharacteristic(race: RaceEnum, dispositions: List[Disposition]) extends Characteristic {
8+
def +(rc: RaceCharacteristic) = {
9+
val combined = dispositions.map(d =>
10+
d + rc.dispositions.find(_.disposition == d.disposition).get)
11+
RaceCharacteristic(race, combined)
12+
}
13+
}
614

7-
case class EthnicityCharacteristic(ethnicity: EthnicityEnum, dispositions: List[Disposition]) extends Characteristic
15+
case class EthnicityCharacteristic(ethnicity: EthnicityEnum, dispositions: List[Disposition]) extends Characteristic {
16+
def +(ec: EthnicityCharacteristic) = {
17+
val combined = dispositions.map(d =>
18+
d + ec.dispositions.find(_.disposition == d.disposition).get)
19+
EthnicityCharacteristic(ethnicity, combined)
20+
}
21+
}
822

9-
case class MinorityStatusCharacteristic(minorityStatus: MinorityStatusEnum, dispositions: List[Disposition]) extends Characteristic
23+
case class MinorityStatusCharacteristic(minorityStatus: MinorityStatusEnum, dispositions: List[Disposition]) extends Characteristic {
24+
def +(msc: MinorityStatusCharacteristic) = {
25+
val combined = dispositions.map(d =>
26+
d + msc.dispositions.find(_.disposition == d.disposition).get)
27+
MinorityStatusCharacteristic(minorityStatus, combined)
28+
}
29+
}
Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package hmda.model.publication.reports
22

33
case class Disposition(
4-
disposition: ActionTakenTypeEnum,
5-
count: Int,
6-
value: Int
7-
)
4+
disposition: ActionTakenTypeEnum,
5+
count: Int,
6+
value: Int
7+
) {
8+
def +(disp: Disposition): Disposition = {
9+
Disposition(disposition, count + disp.count, value + disp.value)
10+
}
11+
}

project/Dependencies.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,7 @@ object Dependencies {
4141
val alpakkaCassandra = "com.lightbend.akka" %% "akka-stream-alpakka-cassandra" % Version.alpakka
4242
val cassandraDriver = "com.datastax.cassandra" % "cassandra-driver-core" % Version.cassandraDriver
4343
val javaMail = "javax.mail" % "mail" % Version.javaMail
44+
val gatlingHighcharts = "io.gatling.highcharts" % "gatling-charts-highcharts" % Version.gatling % "test"
45+
val gatling = "io.gatling" % "gatling-test-framework" % Version.gatling % "test"
46+
4447
}

project/Version.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ object Version {
2323
val alpakka = "0.13"
2424
val cassandraDriver = "3.2.0"
2525
val javaMail = "1.4.7"
26+
val gatling = "2.3.0"
2627
}

project/gatling.sbt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
addSbtPlugin("io.gatling" % "gatling-sbt" % "2.2.2")

publication/src/main/resources/reports-metadata.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ A52,Aggregate,5-2,Received;Originated;ApprovedButNotAccepted;Denied;Withdrawn;Cl
33
A53,Aggregate,5-3,Received;Originated;ApprovedButNotAccepted;Denied;Withdrawn;Closed,"Disposition of Applications to Refinance Loans on 1-to-4 Family and Manufactured Home Dwellings, by Income, Race, and Ethnicity of Applicant"
44
D51,Disclosure,5-1,Received;Originated;ApprovedButNotAccepted;Denied;Withdrawn;Closed,"Disposition of applications for FHA, FSA/RHS, and VA home-purchase loans, 1- to 4-family and manufactured home dwellings, by income, race and ethnicity of applicant"
55
D53,Disclosure,5-3,Received;Originated;ApprovedButNotAccepted;Denied;Withdrawn;Closed,"Disposition of Applications to Refinance Loans on 1-to-4 Family and Manufactured Home Dwellings, by Income, Race, and Ethnicity of Applicant"
6+
N52,National Aggregate,5-2,Received;Originated;ApprovedButNotAccepted;Denied;Withdrawn;Closed,"Disposition of Applications for Conventional Home-Purchase Loans, 1-to-4 Family and Manufactured Home Dwellings, by Income, Race, and Ethnicity of Applicant"

0 commit comments

Comments
 (0)