SlideShare a Scribd company logo
Performance tests with
Gatling
Andrzej Ludwikowski
About me
➔
➔ aludwikowski.blogspot.com
➔ github.com/aludwiko
➔ @aludwikowski
Performance tests - why so hard?
Simulate production as close as possible:
● hardware
○ CPU, RAM, storage, ...
● software
○ OS, Virtualization, DBs, …
● load
● isolation
Performance tests - why so hard?
Necessary to have:
● infrastructure
● monitoring
● logging
Performance tests - why so hard?
● Your performance intuition is wrong!
1. collect the data (monitoring, logging, profiling)
2. find the bottleneck (based on data)
3. fix the bottleneck
4. collect the data and check the assumptions
5. go to 1.
Performance tests - why so hard?
Lies, damned lies, and statistics:
● arithmetic mean = 2.9
● median = 1
● standard deviation = 6 (only for normal distribution)
Performance tests - why so hard?
● Anscombe's quartet
http://bravenewgeek.com/tag/coordinated-omission/
Property Value
Mean of x 9
Sample variance of x 11
Mean of y 7.50
Sample variance of y 4.125
Correlation between x and y 0.816
Linear regression line y = 3 + 0.5x
Coefficient of determination of
the linear regression
0.67
Performance tests - why so hard?
Lies, damned lies, and statistics:
● arithmetic mean = 2.9
● median = 1
● standard deviation = 6 (only for normal distribution)
Use:
● percentiles
○ 50th = 1
○ 70th = 1
○ 90th = 2.9
○ 95th = 11.45
○ 99th = 18.29
Check percentiles implementation!
Performance tests - why so hard?
● Coordinated omission problem by Gil Tene
http://bravenewgeek.com/tag/coordinated-omission/
Coordinated omission problem
http://bravenewgeek.com/tag/coordinated-omission/
system.exit(0)
Coordinated omission problem
http://bravenewgeek.com/tag/coordinated-omission/
system.exit(0)
samples 31
50 th 1
70 th 1
90 th 1
95 th 1
99 th 12.89
99,9 th 28.30
99,99 th 29.82
avg 1.93
Coordinated omission problem
http://bravenewgeek.com/tag/coordinated-omission/
system.exit(0)
samples 10001
50 th 1
70 th 1
90 th 1
95 th 1
99 th 1
99,9 th 1.15
99,99 th 28.01
avg 1.03
Coordinated omission problem
http://bravenewgeek.com/tag/coordinated-omission/
system.exit(0)
Coordinated omission problem
http://bravenewgeek.com/tag/coordinated-omission/
system.exit(0)
samples 60
50 th 1
70 th 12.6
90 th 24.3
95 th 27.2
99 th 29.45
99,9 th 29.95
99,99 th 29.99
avg 8.25
Coordinated omission problem
Coordinated omission problem
total time 60 s
max 30 s
99th 1 s
Coordinated omission problem
total time 60 s
max 30 s
99th 1 s
time in % for max 50%
Coordinated omission problem
total time 60 s
max 30 s
99th 1 s
time in % for max 50%
expected time in % for 99th 50% - 1% = 49%
Coordinated omission problem
total time 60 s
max 30 s
99th 1 s
time in % for max 50%
expected time in % for 99th 50% - 1% = 49%
real time for 99th 60 s * 49% = 29.4 s
Coordinated omission problem
http://bravenewgeek.com/tag/coordinated-omission/
Performance tests - why so hard?
● Coordinated omission vs Gatling
http://bravenewgeek.com/tag/coordinated-omission/
Performance tests - why so hard?
● Tests…
Performance tests - why so hard?
● Tests…
Gatling
Gatling
Why Gatling?
● non-blocking, asynchronous stack (scala, akka, netty)
● scala !!!111oneoneone (maven, sbt support)
● DSL
● recorder
● math is good
● reports
● integration & performance tests
Gatling DSL
class BasicSimulation extends Simulation {
val httpConf = http
.baseURL("http://computer-database.gatling.io")
.acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
.acceptEncodingHeader("gzip, deflate")
.acceptLanguageHeader("en-US,en;q=0.5")
.userAgentHeader("Mozilla/5.0 (Macintosh; X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0")
val firstScenario = scenario("First Scenario Name")
.exec(http("Request name").get("/"))
.pause(7)
setUp(firstScenario.inject(atOnceUsers(1)).protocols(httpConf))
}
Gatling DSL
class FirstScenarioV2 extends MySimulation {
val firstScenario = scenario("First Scenario Name")
.exec(http("Go to root page").get("/"))
.pause(7 seconds)
setUp(firstScenario.inject(atOnceUsers(1)).protocols(httpConf))
}
Gatling DSL
import io.gatling.core.Predef._
import io.gatling.http.Predef._
class FirstScenarioV2 extends MySimulation {
val firstScenario = scenario("First Scenario Name")
.exec(http("Go to root page").get("/"))
.pause(7 seconds)
setUp(firstScenario.inject(atOnceUsers(1)).protocols(httpConf))
}
Gatling DSL
class ComplexScenario extends MySimulation {
val complexScenario = scenario("Complex demo scenario")
.exec(http("request_1").get("/")).pause(7)
.exec(http("request_2").get("/computers?f=macbook")).pause(2)
.exec(http("request_3").get("/computers/6")).pause(3)
.exec(http("request_4").get("/")).pause(2)
.exec(http("request_5").get("/computers?p=1")).pause(670 milliseconds)
.exec(http("request_6").get("/computers/new")).pause(1)
.exec(http("request_7")
.post("/computers")
.formParam("name", "MyComputer").formParam("introduced", "2012-05-30").formParam("company", "37"))
setUp(complexScenario.inject(atOnceUsers(1)).protocols(httpConf))
}
Gatling DSL
class ComplexScenarioV2 extends MySimulation {
val complexScenario = scenario("Complex demo scenario")
.exec(goToRootPage).pause(7)
.exec(searchFor("macbook")).pause(2)
.exec(positionAt(6)).pause(3)
.exec(goToRootPage).pause(2)
.exec(goToPage(1)).pause(670 milliseconds)
.exec(openNewComputerForm).pause(1)
.exec(addNewComputer)
setUp(complexScenario.inject(atOnceUsers(1)).protocols(httpConf))
}
Gatling DSL
class ComplexScenarioV3 extends MySimulation {
val search = exec(goToRootPage).pause(7)
.exec(searchFor("macbook")).pause(2)
.exec(positionAt(6)).pause(3)
val addComputer = exec(goToRootPage).pause(2)
.exec(goToPage(1)).pause(670 milliseconds)
.exec(openNewComputerForm).pause(1)
.exec(addNewComputer)
val complexScenario = scenario("Complex demo scenario").exec(search, addComputer)
setUp(complexScenario.inject(atOnceUsers(1)).protocols(httpConf))
}
Gatling DSL - checks
scenario("DSL demo")
.exec(http("go to page")
.get("/computers")
.check(status.is(200))
.check(status.in(200 to 210))
Gatling DSL - checks
scenario("DSL demo")
.exec(http("go to page")
.get("/computers")
.check(regex("computers")
.find(1)
.exists)
https://www.pinterest.com/pin/491666484294006138/
Gatling DSL - checks
scenario("First Scenario Name")
.exec(http("Request name")
.get("/computers")
.check(jsonPath("$..foo.bar[2].baz").ofType[String].notExists)
.check(xpath("//input[@id='text1']/@value").is("test"))
.check(css("...").transform(_.split('|').toSeq).is(Seq("1", "2")))
Gatling DSL - checks
scenario("DSL demo")
.exec(http("Authorize")
.get("/auth")
.check(regex("token").find(1).exists
.saveAs("authorizationToken")))
.exec(http("Authorized resource")
.get("/authorized_resource?token=${authorizationToken}"))
Gatling DSL - looping
repeat(5, "i") {
exec(goToPage("${i}".toInt))
.pause(1)
}
● repeat
● foreach
● during
● asLongAs
● forever
https://blog.hubspot.com/blog/tabid/6307/bid/32019/Why-Every-Marketer-Needs-Closed-Loop-Reporting.aspx#sm.0005lrqj811waf3ntmn1cul3881gr
Gatling DSL - polling
exec(
polling
.every(10 seconds)
.exec(searchFor("thinkpad"))
)
http://www.firmus-solutions.com/terms-conditions.html
Gatling DSL - conditions
doIf(session => session("user").as[String].startsWith("admin")) {
exec(goToAdminPage)
}
● doIf
● doIfEquals
● doIfOrElse
● doSwitch
● doSwitchOrElse
● randomSwitch
https://en.wikipedia.org/wiki/Decision_tree
Gatling DSL - error management
exec(sendMoney)
.tryMax(10){
exec(checkIfMoneyReceived)
}
● tryMax
● exitBlockOnFail
● exitHereIfFailed
Alice Bob
Kafka
Gatling DSL - setup
setUp(myScenario
.inject(
nothingFor(4 seconds),
atOnceUsers(10),
rampUsers(10) over (5 seconds))
.protocols(httpConf))
.maxDuration(10 minutes)
● constantUsersPerSec
● rampUsersPerSec
● splitUsers
● heavisideUsers
Gatling DSL - setup
setUp(myScenario
.inject(atOnceUsers(10))
.protocols(httpConf))
.assertions(
global.responseTime.max.lt(50),
global.failedRequests.percent.is(0)
)
http://englishthroughlaxas.blogspot.com/2015/07/531-24-expression-of-assertion-emphasis.html
Gatling DSL - setup
setUp(myScenario
.inject(atOnceUsers(10))
.protocols(httpConf))
.throttle(
reachRps(100) in (30 second),
holdFor(1 minute),
jumpToRps(50),
holdFor(2 hours)
)
Gatling DSL - feeders
val companies = List("apple", "lenovo", "hp")
val feeder = Iterator.continually(
Map("company" -> companies(Random.nextInt(companies.size))))
val searching = scenario("Searching")
.feed(feeder)
.exec(searchFor("${company}"))
● RecordSeqFeederBuilder
● CSV
● JSON
● JDBC
● Sitemap
● Redis
● … http://favim.com/orig/201104/23/Favim.com-22725.jpg
Gatling DSL - resource inferring
val httpConf = http
.baseURL("http://computer-database.gatling.io")
.acceptHeader("...")
.acceptEncodingHeader("...")
.acceptLanguageHeader("...")
.inferHtmlResources()
.userAgentHeader("...")
Gatling - launching
● gatling:test
● gatling:testOnly x.y.z.GetUserSimulation
Gatling DSL - other goodies
● Custom validators
● HTTP
○ SSL
○ SSE (Server Sent Event)
○ basic cookies support
● WebSocket
● JMS
● Pluginable architecture:
○ cassandra plugin
○ kafka plugin
○ rabbitMQ
○ AMQP
Gatling DSL - logging/debugging
● logging ALL HTTP request and responses
<logger name="io.gatling.http.ahc" level="TRACE" />
<logger name="io.gatling.http.response" level="TRACE" />
● logging ONLY FAILED HTTP request and responses
<logger name="io.gatling.http.ahc" level="DEBUG" />
<logger name="io.gatling.http.response" level="DEBUG" />
Gatling DSL - reporting
================================================================================
---- Global Information --------------------------------------------------------
> request count 10 (OK=10 KO=0 )
> min response time 40 (OK=40 KO=- )
> max response time 177 (OK=177 KO=- )
> mean response time 55 (OK=55 KO=- )
> std deviation 41 (OK=41 KO=- )
> response time 50th percentile 42 (OK=42 KO=- )
> response time 75th percentile 43 (OK=43 KO=- )
> response time 95th percentile 117 (OK=117 KO=- )
> response time 99th percentile 165 (OK=165 KO=- )
> mean requests/sec 0.909 (OK=0.909 KO=- )
---- Response Time Distribution ------------------------------------------------
> t < 800 ms 10 (100%)
> 800 ms < t < 1200 ms 0 ( 0%)
> t > 1200 ms 0 ( 0%)
> failed 0 ( 0%)
================================================================================
Gatling DSL - distributed
Gatling DSL - distributed
IT system
Gatling DSL - distributed
● manually
● Gatling FrontLine
● Flood.io
IT system
About me
➔
➔ aludwikowski.blogspot.com
➔ github.com/aludwiko
➔ @aludwikowski

More Related Content

What's hot

Metodología scrum
Metodología scrumMetodología scrum
Metodología scrum
Trisca Dominic
 
What Is Agile Scrum
What Is Agile ScrumWhat Is Agile Scrum
What Is Agile Scrum
Michael Bourque
 
Introduction to Scrum
Introduction to ScrumIntroduction to Scrum
Introduction to Scrum
Sriram Srinivasan
 
Clouds and Tools: Cheat Sheets & Infographics
Clouds and Tools: Cheat Sheets & InfographicsClouds and Tools: Cheat Sheets & Infographics
Clouds and Tools: Cheat Sheets & Infographics
Thomas Poetter
 
Apply MLOps at Scale
Apply MLOps at ScaleApply MLOps at Scale
Apply MLOps at Scale
Databricks
 
Management fundamentals scrum 101
Management fundamentals scrum 101Management fundamentals scrum 101
Management fundamentals scrum 101
Bar-Ezer Yossi
 
Scrum 101: Introduction to Scrum
Scrum 101: Introduction to ScrumScrum 101: Introduction to Scrum
Scrum 101: Introduction to Scrum
Arrielle Mali
 
Metodología agile scrum
Metodología agile scrum Metodología agile scrum
Metodología agile scrum
Αλβάρο Κονδε Οργαζ
 
Technical stories v1.2
Technical stories v1.2Technical stories v1.2
Technical stories v1.2
Jim Brisson
 
Waterfall vs agile approach scrum framework and best practices in software d...
Waterfall vs agile approach  scrum framework and best practices in software d...Waterfall vs agile approach  scrum framework and best practices in software d...
Waterfall vs agile approach scrum framework and best practices in software d...
Tayfun Bilsel
 
Scrum
ScrumScrum
Agile Process Introduction
Agile Process IntroductionAgile Process Introduction
Agile Process Introduction
Nguyen Hai
 
Scrum
ScrumScrum
Scrum Events and Artifacts in Action
Scrum Events and Artifacts in ActionScrum Events and Artifacts in Action
Scrum Events and Artifacts in Action
Lemi Orhan Ergin
 
Streaming sql and druid
Streaming sql and druid Streaming sql and druid
Streaming sql and druid
arupmalakar
 
Airflow 101
Airflow 101Airflow 101
Airflow 101
SaarBergerbest
 
Agile evolution lifecycle - From implementing Agile to being Agile
Agile evolution lifecycle - From implementing Agile to being AgileAgile evolution lifecycle - From implementing Agile to being Agile
Agile evolution lifecycle - From implementing Agile to being Agile
Michal Epstein
 
Agile Transformation Strategy
Agile Transformation StrategyAgile Transformation Strategy
Agile Transformation Strategy
Semen Arslan
 
Agile Software Development
Agile Software DevelopmentAgile Software Development
Agile Software Development
Sachith Perera
 
What is the purpose of Sprint planning meeting in Agile?
What is the purpose of Sprint planning meeting in Agile?What is the purpose of Sprint planning meeting in Agile?
What is the purpose of Sprint planning meeting in Agile?
Mario Lucero
 

What's hot (20)

Metodología scrum
Metodología scrumMetodología scrum
Metodología scrum
 
What Is Agile Scrum
What Is Agile ScrumWhat Is Agile Scrum
What Is Agile Scrum
 
Introduction to Scrum
Introduction to ScrumIntroduction to Scrum
Introduction to Scrum
 
Clouds and Tools: Cheat Sheets & Infographics
Clouds and Tools: Cheat Sheets & InfographicsClouds and Tools: Cheat Sheets & Infographics
Clouds and Tools: Cheat Sheets & Infographics
 
Apply MLOps at Scale
Apply MLOps at ScaleApply MLOps at Scale
Apply MLOps at Scale
 
Management fundamentals scrum 101
Management fundamentals scrum 101Management fundamentals scrum 101
Management fundamentals scrum 101
 
Scrum 101: Introduction to Scrum
Scrum 101: Introduction to ScrumScrum 101: Introduction to Scrum
Scrum 101: Introduction to Scrum
 
Metodología agile scrum
Metodología agile scrum Metodología agile scrum
Metodología agile scrum
 
Technical stories v1.2
Technical stories v1.2Technical stories v1.2
Technical stories v1.2
 
Waterfall vs agile approach scrum framework and best practices in software d...
Waterfall vs agile approach  scrum framework and best practices in software d...Waterfall vs agile approach  scrum framework and best practices in software d...
Waterfall vs agile approach scrum framework and best practices in software d...
 
Scrum
ScrumScrum
Scrum
 
Agile Process Introduction
Agile Process IntroductionAgile Process Introduction
Agile Process Introduction
 
Scrum
ScrumScrum
Scrum
 
Scrum Events and Artifacts in Action
Scrum Events and Artifacts in ActionScrum Events and Artifacts in Action
Scrum Events and Artifacts in Action
 
Streaming sql and druid
Streaming sql and druid Streaming sql and druid
Streaming sql and druid
 
Airflow 101
Airflow 101Airflow 101
Airflow 101
 
Agile evolution lifecycle - From implementing Agile to being Agile
Agile evolution lifecycle - From implementing Agile to being AgileAgile evolution lifecycle - From implementing Agile to being Agile
Agile evolution lifecycle - From implementing Agile to being Agile
 
Agile Transformation Strategy
Agile Transformation StrategyAgile Transformation Strategy
Agile Transformation Strategy
 
Agile Software Development
Agile Software DevelopmentAgile Software Development
Agile Software Development
 
What is the purpose of Sprint planning meeting in Agile?
What is the purpose of Sprint planning meeting in Agile?What is the purpose of Sprint planning meeting in Agile?
What is the purpose of Sprint planning meeting in Agile?
 

Similar to Performance tests with Gatling (extended)

Stress test your backend with Gatling
Stress test your backend with GatlingStress test your backend with Gatling
Stress test your backend with Gatling
Andrzej Ludwikowski
 
Performance tests - it's a trap
Performance tests - it's a trapPerformance tests - it's a trap
Performance tests - it's a trap
Andrzej Ludwikowski
 
Performance tests with Gatling
Performance tests with GatlingPerformance tests with Gatling
Performance tests with Gatling
Andrzej Ludwikowski
 
Token Testing Slides
Token  Testing SlidesToken  Testing Slides
Token Testing Slides
ericholscher
 
CBDW2014 - MockBox, get ready to mock your socks off!
CBDW2014 - MockBox, get ready to mock your socks off!CBDW2014 - MockBox, get ready to mock your socks off!
CBDW2014 - MockBox, get ready to mock your socks off!
Ortus Solutions, Corp
 
ScalaCheck
ScalaCheckScalaCheck
ScalaCheck
BeScala
 
Testing ASP.NET - Progressive.NET
Testing ASP.NET - Progressive.NETTesting ASP.NET - Progressive.NET
Testing ASP.NET - Progressive.NET
Ben Hall
 
Good Practices On Test Automation
Good Practices On Test AutomationGood Practices On Test Automation
Good Practices On Test Automation
Gustavo Labbate Godoy
 
Performance tests with gatling
Performance tests with gatlingPerformance tests with gatling
Performance tests with gatling
SoftwareMill
 
Django’s nasal passage
Django’s nasal passageDjango’s nasal passage
Django’s nasal passage
Erik Rose
 
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMockUnit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
Robot Media
 
Automated Frontend Testing
Automated Frontend TestingAutomated Frontend Testing
Automated Frontend Testing
Neil Crosby
 
Pocket Talk; Spock framework
Pocket Talk; Spock frameworkPocket Talk; Spock framework
Pocket Talk; Spock framework
Infoway
 
Spock framework
Spock frameworkSpock framework
Spock framework
Djair Carvalho
 
Thomas Fuchs Presentation
Thomas Fuchs PresentationThomas Fuchs Presentation
Thomas Fuchs Presentation
RubyOnRails_dude
 
How to fake_properly
How to fake_properlyHow to fake_properly
How to fake_properly
Rainer Schuettengruber
 
Heavenly hell – automated tests at scale wojciech seliga
Heavenly hell – automated tests at scale   wojciech seligaHeavenly hell – automated tests at scale   wojciech seliga
Heavenly hell – automated tests at scale wojciech seliga
Atlassian
 
Testing and validating spark programs - Strata SJ 2016
Testing and validating spark programs - Strata SJ 2016Testing and validating spark programs - Strata SJ 2016
Testing and validating spark programs - Strata SJ 2016
Holden Karau
 
Mutant Tests Too: The SQL
Mutant Tests Too: The SQLMutant Tests Too: The SQL
Mutant Tests Too: The SQL
DataWorks Summit
 
Test First Teaching
Test First TeachingTest First Teaching
Test First Teaching
Sarah Allen
 

Similar to Performance tests with Gatling (extended) (20)

Stress test your backend with Gatling
Stress test your backend with GatlingStress test your backend with Gatling
Stress test your backend with Gatling
 
Performance tests - it's a trap
Performance tests - it's a trapPerformance tests - it's a trap
Performance tests - it's a trap
 
Performance tests with Gatling
Performance tests with GatlingPerformance tests with Gatling
Performance tests with Gatling
 
Token Testing Slides
Token  Testing SlidesToken  Testing Slides
Token Testing Slides
 
CBDW2014 - MockBox, get ready to mock your socks off!
CBDW2014 - MockBox, get ready to mock your socks off!CBDW2014 - MockBox, get ready to mock your socks off!
CBDW2014 - MockBox, get ready to mock your socks off!
 
ScalaCheck
ScalaCheckScalaCheck
ScalaCheck
 
Testing ASP.NET - Progressive.NET
Testing ASP.NET - Progressive.NETTesting ASP.NET - Progressive.NET
Testing ASP.NET - Progressive.NET
 
Good Practices On Test Automation
Good Practices On Test AutomationGood Practices On Test Automation
Good Practices On Test Automation
 
Performance tests with gatling
Performance tests with gatlingPerformance tests with gatling
Performance tests with gatling
 
Django’s nasal passage
Django’s nasal passageDjango’s nasal passage
Django’s nasal passage
 
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMockUnit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
 
Automated Frontend Testing
Automated Frontend TestingAutomated Frontend Testing
Automated Frontend Testing
 
Pocket Talk; Spock framework
Pocket Talk; Spock frameworkPocket Talk; Spock framework
Pocket Talk; Spock framework
 
Spock framework
Spock frameworkSpock framework
Spock framework
 
Thomas Fuchs Presentation
Thomas Fuchs PresentationThomas Fuchs Presentation
Thomas Fuchs Presentation
 
How to fake_properly
How to fake_properlyHow to fake_properly
How to fake_properly
 
Heavenly hell – automated tests at scale wojciech seliga
Heavenly hell – automated tests at scale   wojciech seligaHeavenly hell – automated tests at scale   wojciech seliga
Heavenly hell – automated tests at scale wojciech seliga
 
Testing and validating spark programs - Strata SJ 2016
Testing and validating spark programs - Strata SJ 2016Testing and validating spark programs - Strata SJ 2016
Testing and validating spark programs - Strata SJ 2016
 
Mutant Tests Too: The SQL
Mutant Tests Too: The SQLMutant Tests Too: The SQL
Mutant Tests Too: The SQL
 
Test First Teaching
Test First TeachingTest First Teaching
Test First Teaching
 

More from Andrzej Ludwikowski

Event Sourcing - what could go wrong - Devoxx BE
Event Sourcing - what could go wrong - Devoxx BEEvent Sourcing - what could go wrong - Devoxx BE
Event Sourcing - what could go wrong - Devoxx BE
Andrzej Ludwikowski
 
Event Sourcing - what could go wrong - Jfokus 2022
Event Sourcing - what could go wrong - Jfokus 2022Event Sourcing - what could go wrong - Jfokus 2022
Event Sourcing - what could go wrong - Jfokus 2022
Andrzej Ludwikowski
 
Event sourcing - what could possibly go wrong ? Devoxx PL 2021
Event sourcing  - what could possibly go wrong ? Devoxx PL 2021Event sourcing  - what could possibly go wrong ? Devoxx PL 2021
Event sourcing - what could possibly go wrong ? Devoxx PL 2021
Andrzej Ludwikowski
 
Event Sourcing - what could possibly go wrong?
Event Sourcing - what could possibly go wrong?Event Sourcing - what could possibly go wrong?
Event Sourcing - what could possibly go wrong?
Andrzej Ludwikowski
 
Cassandra lesson learned - extended
Cassandra   lesson learned  - extendedCassandra   lesson learned  - extended
Cassandra lesson learned - extended
Andrzej Ludwikowski
 
Cassandra - lesson learned
Cassandra  - lesson learnedCassandra  - lesson learned
Cassandra - lesson learned
Andrzej Ludwikowski
 
Annotation processing tool
Annotation processing toolAnnotation processing tool
Annotation processing tool
Andrzej Ludwikowski
 

More from Andrzej Ludwikowski (7)

Event Sourcing - what could go wrong - Devoxx BE
Event Sourcing - what could go wrong - Devoxx BEEvent Sourcing - what could go wrong - Devoxx BE
Event Sourcing - what could go wrong - Devoxx BE
 
Event Sourcing - what could go wrong - Jfokus 2022
Event Sourcing - what could go wrong - Jfokus 2022Event Sourcing - what could go wrong - Jfokus 2022
Event Sourcing - what could go wrong - Jfokus 2022
 
Event sourcing - what could possibly go wrong ? Devoxx PL 2021
Event sourcing  - what could possibly go wrong ? Devoxx PL 2021Event sourcing  - what could possibly go wrong ? Devoxx PL 2021
Event sourcing - what could possibly go wrong ? Devoxx PL 2021
 
Event Sourcing - what could possibly go wrong?
Event Sourcing - what could possibly go wrong?Event Sourcing - what could possibly go wrong?
Event Sourcing - what could possibly go wrong?
 
Cassandra lesson learned - extended
Cassandra   lesson learned  - extendedCassandra   lesson learned  - extended
Cassandra lesson learned - extended
 
Cassandra - lesson learned
Cassandra  - lesson learnedCassandra  - lesson learned
Cassandra - lesson learned
 
Annotation processing tool
Annotation processing toolAnnotation processing tool
Annotation processing tool
 

Recently uploaded

Mobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona InfotechMobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona Infotech
Drona Infotech
 
GreenCode-A-VSCode-Plugin--Dario-Jurisic
GreenCode-A-VSCode-Plugin--Dario-JurisicGreenCode-A-VSCode-Plugin--Dario-Jurisic
GreenCode-A-VSCode-Plugin--Dario-Jurisic
Green Software Development
 
在线购买加拿大英属哥伦比亚大学毕业证本科学位证书原版一模一样
在线购买加拿大英属哥伦比亚大学毕业证本科学位证书原版一模一样在线购买加拿大英属哥伦比亚大学毕业证本科学位证书原版一模一样
在线购买加拿大英属哥伦比亚大学毕业证本科学位证书原版一模一样
mz5nrf0n
 
Malibou Pitch Deck For Its €3M Seed Round
Malibou Pitch Deck For Its €3M Seed RoundMalibou Pitch Deck For Its €3M Seed Round
Malibou Pitch Deck For Its €3M Seed Round
sjcobrien
 
UI5con 2024 - Keynote: Latest News about UI5 and it’s Ecosystem
UI5con 2024 - Keynote: Latest News about UI5 and it’s EcosystemUI5con 2024 - Keynote: Latest News about UI5 and it’s Ecosystem
UI5con 2024 - Keynote: Latest News about UI5 and it’s Ecosystem
Peter Muessig
 
Top Benefits of Using Salesforce Healthcare CRM for Patient Management.pdf
Top Benefits of Using Salesforce Healthcare CRM for Patient Management.pdfTop Benefits of Using Salesforce Healthcare CRM for Patient Management.pdf
Top Benefits of Using Salesforce Healthcare CRM for Patient Management.pdf
VALiNTRY360
 
原版定制美国纽约州立大学奥尔巴尼分校毕业证学位证书原版一模一样
原版定制美国纽约州立大学奥尔巴尼分校毕业证学位证书原版一模一样原版定制美国纽约州立大学奥尔巴尼分校毕业证学位证书原版一模一样
原版定制美国纽约州立大学奥尔巴尼分校毕业证学位证书原版一模一样
mz5nrf0n
 
E-Invoicing Implementation: A Step-by-Step Guide for Saudi Arabian Companies
E-Invoicing Implementation: A Step-by-Step Guide for Saudi Arabian CompaniesE-Invoicing Implementation: A Step-by-Step Guide for Saudi Arabian Companies
E-Invoicing Implementation: A Step-by-Step Guide for Saudi Arabian Companies
Quickdice ERP
 
Measures in SQL (SIGMOD 2024, Santiago, Chile)
Measures in SQL (SIGMOD 2024, Santiago, Chile)Measures in SQL (SIGMOD 2024, Santiago, Chile)
Measures in SQL (SIGMOD 2024, Santiago, Chile)
Julian Hyde
 
E-commerce Development Services- Hornet Dynamics
E-commerce Development Services- Hornet DynamicsE-commerce Development Services- Hornet Dynamics
E-commerce Development Services- Hornet Dynamics
Hornet Dynamics
 
SQL Accounting Software Brochure Malaysia
SQL Accounting Software Brochure MalaysiaSQL Accounting Software Brochure Malaysia
SQL Accounting Software Brochure Malaysia
GohKiangHock
 
Oracle 23c New Features For DBAs and Developers.pptx
Oracle 23c New Features For DBAs and Developers.pptxOracle 23c New Features For DBAs and Developers.pptx
Oracle 23c New Features For DBAs and Developers.pptx
Remote DBA Services
 
fiscal year variant fiscal year variant.
fiscal year variant fiscal year variant.fiscal year variant fiscal year variant.
fiscal year variant fiscal year variant.
AnkitaPandya11
 
zOS Mainframe JES2-JES3 JCL-JECL Differences
zOS Mainframe JES2-JES3 JCL-JECL DifferenceszOS Mainframe JES2-JES3 JCL-JECL Differences
zOS Mainframe JES2-JES3 JCL-JECL Differences
YousufSait3
 
How Can Hiring A Mobile App Development Company Help Your Business Grow?
How Can Hiring A Mobile App Development Company Help Your Business Grow?How Can Hiring A Mobile App Development Company Help Your Business Grow?
How Can Hiring A Mobile App Development Company Help Your Business Grow?
ToXSL Technologies
 
Enums On Steroids - let's look at sealed classes !
Enums On Steroids - let's look at sealed classes !Enums On Steroids - let's look at sealed classes !
Enums On Steroids - let's look at sealed classes !
Marcin Chrost
 
Modelling Up - DDDEurope 2024 - Amsterdam
Modelling Up - DDDEurope 2024 - AmsterdamModelling Up - DDDEurope 2024 - Amsterdam
Modelling Up - DDDEurope 2024 - Amsterdam
Alberto Brandolini
 
ALGIT - Assembly Line for Green IT - Numbers, Data, Facts
ALGIT - Assembly Line for Green IT - Numbers, Data, FactsALGIT - Assembly Line for Green IT - Numbers, Data, Facts
ALGIT - Assembly Line for Green IT - Numbers, Data, Facts
Green Software Development
 
Artificia Intellicence and XPath Extension Functions
Artificia Intellicence and XPath Extension FunctionsArtificia Intellicence and XPath Extension Functions
Artificia Intellicence and XPath Extension Functions
Octavian Nadolu
 
Oracle Database 19c New Features for DBAs and Developers.pptx
Oracle Database 19c New Features for DBAs and Developers.pptxOracle Database 19c New Features for DBAs and Developers.pptx
Oracle Database 19c New Features for DBAs and Developers.pptx
Remote DBA Services
 

Recently uploaded (20)

Mobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona InfotechMobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona Infotech
 
GreenCode-A-VSCode-Plugin--Dario-Jurisic
GreenCode-A-VSCode-Plugin--Dario-JurisicGreenCode-A-VSCode-Plugin--Dario-Jurisic
GreenCode-A-VSCode-Plugin--Dario-Jurisic
 
在线购买加拿大英属哥伦比亚大学毕业证本科学位证书原版一模一样
在线购买加拿大英属哥伦比亚大学毕业证本科学位证书原版一模一样在线购买加拿大英属哥伦比亚大学毕业证本科学位证书原版一模一样
在线购买加拿大英属哥伦比亚大学毕业证本科学位证书原版一模一样
 
Malibou Pitch Deck For Its €3M Seed Round
Malibou Pitch Deck For Its €3M Seed RoundMalibou Pitch Deck For Its €3M Seed Round
Malibou Pitch Deck For Its €3M Seed Round
 
UI5con 2024 - Keynote: Latest News about UI5 and it’s Ecosystem
UI5con 2024 - Keynote: Latest News about UI5 and it’s EcosystemUI5con 2024 - Keynote: Latest News about UI5 and it’s Ecosystem
UI5con 2024 - Keynote: Latest News about UI5 and it’s Ecosystem
 
Top Benefits of Using Salesforce Healthcare CRM for Patient Management.pdf
Top Benefits of Using Salesforce Healthcare CRM for Patient Management.pdfTop Benefits of Using Salesforce Healthcare CRM for Patient Management.pdf
Top Benefits of Using Salesforce Healthcare CRM for Patient Management.pdf
 
原版定制美国纽约州立大学奥尔巴尼分校毕业证学位证书原版一模一样
原版定制美国纽约州立大学奥尔巴尼分校毕业证学位证书原版一模一样原版定制美国纽约州立大学奥尔巴尼分校毕业证学位证书原版一模一样
原版定制美国纽约州立大学奥尔巴尼分校毕业证学位证书原版一模一样
 
E-Invoicing Implementation: A Step-by-Step Guide for Saudi Arabian Companies
E-Invoicing Implementation: A Step-by-Step Guide for Saudi Arabian CompaniesE-Invoicing Implementation: A Step-by-Step Guide for Saudi Arabian Companies
E-Invoicing Implementation: A Step-by-Step Guide for Saudi Arabian Companies
 
Measures in SQL (SIGMOD 2024, Santiago, Chile)
Measures in SQL (SIGMOD 2024, Santiago, Chile)Measures in SQL (SIGMOD 2024, Santiago, Chile)
Measures in SQL (SIGMOD 2024, Santiago, Chile)
 
E-commerce Development Services- Hornet Dynamics
E-commerce Development Services- Hornet DynamicsE-commerce Development Services- Hornet Dynamics
E-commerce Development Services- Hornet Dynamics
 
SQL Accounting Software Brochure Malaysia
SQL Accounting Software Brochure MalaysiaSQL Accounting Software Brochure Malaysia
SQL Accounting Software Brochure Malaysia
 
Oracle 23c New Features For DBAs and Developers.pptx
Oracle 23c New Features For DBAs and Developers.pptxOracle 23c New Features For DBAs and Developers.pptx
Oracle 23c New Features For DBAs and Developers.pptx
 
fiscal year variant fiscal year variant.
fiscal year variant fiscal year variant.fiscal year variant fiscal year variant.
fiscal year variant fiscal year variant.
 
zOS Mainframe JES2-JES3 JCL-JECL Differences
zOS Mainframe JES2-JES3 JCL-JECL DifferenceszOS Mainframe JES2-JES3 JCL-JECL Differences
zOS Mainframe JES2-JES3 JCL-JECL Differences
 
How Can Hiring A Mobile App Development Company Help Your Business Grow?
How Can Hiring A Mobile App Development Company Help Your Business Grow?How Can Hiring A Mobile App Development Company Help Your Business Grow?
How Can Hiring A Mobile App Development Company Help Your Business Grow?
 
Enums On Steroids - let's look at sealed classes !
Enums On Steroids - let's look at sealed classes !Enums On Steroids - let's look at sealed classes !
Enums On Steroids - let's look at sealed classes !
 
Modelling Up - DDDEurope 2024 - Amsterdam
Modelling Up - DDDEurope 2024 - AmsterdamModelling Up - DDDEurope 2024 - Amsterdam
Modelling Up - DDDEurope 2024 - Amsterdam
 
ALGIT - Assembly Line for Green IT - Numbers, Data, Facts
ALGIT - Assembly Line for Green IT - Numbers, Data, FactsALGIT - Assembly Line for Green IT - Numbers, Data, Facts
ALGIT - Assembly Line for Green IT - Numbers, Data, Facts
 
Artificia Intellicence and XPath Extension Functions
Artificia Intellicence and XPath Extension FunctionsArtificia Intellicence and XPath Extension Functions
Artificia Intellicence and XPath Extension Functions
 
Oracle Database 19c New Features for DBAs and Developers.pptx
Oracle Database 19c New Features for DBAs and Developers.pptxOracle Database 19c New Features for DBAs and Developers.pptx
Oracle Database 19c New Features for DBAs and Developers.pptx
 

Performance tests with Gatling (extended)

  • 2. About me ➔ ➔ aludwikowski.blogspot.com ➔ github.com/aludwiko ➔ @aludwikowski
  • 3.
  • 4. Performance tests - why so hard? Simulate production as close as possible: ● hardware ○ CPU, RAM, storage, ... ● software ○ OS, Virtualization, DBs, … ● load ● isolation
  • 5. Performance tests - why so hard? Necessary to have: ● infrastructure ● monitoring ● logging
  • 6. Performance tests - why so hard? ● Your performance intuition is wrong! 1. collect the data (monitoring, logging, profiling) 2. find the bottleneck (based on data) 3. fix the bottleneck 4. collect the data and check the assumptions 5. go to 1.
  • 7. Performance tests - why so hard? Lies, damned lies, and statistics: ● arithmetic mean = 2.9 ● median = 1 ● standard deviation = 6 (only for normal distribution)
  • 8. Performance tests - why so hard? ● Anscombe's quartet http://bravenewgeek.com/tag/coordinated-omission/ Property Value Mean of x 9 Sample variance of x 11 Mean of y 7.50 Sample variance of y 4.125 Correlation between x and y 0.816 Linear regression line y = 3 + 0.5x Coefficient of determination of the linear regression 0.67
  • 9. Performance tests - why so hard? Lies, damned lies, and statistics: ● arithmetic mean = 2.9 ● median = 1 ● standard deviation = 6 (only for normal distribution) Use: ● percentiles ○ 50th = 1 ○ 70th = 1 ○ 90th = 2.9 ○ 95th = 11.45 ○ 99th = 18.29 Check percentiles implementation!
  • 10. Performance tests - why so hard? ● Coordinated omission problem by Gil Tene http://bravenewgeek.com/tag/coordinated-omission/
  • 12. Coordinated omission problem http://bravenewgeek.com/tag/coordinated-omission/ system.exit(0) samples 31 50 th 1 70 th 1 90 th 1 95 th 1 99 th 12.89 99,9 th 28.30 99,99 th 29.82 avg 1.93
  • 13. Coordinated omission problem http://bravenewgeek.com/tag/coordinated-omission/ system.exit(0) samples 10001 50 th 1 70 th 1 90 th 1 95 th 1 99 th 1 99,9 th 1.15 99,99 th 28.01 avg 1.03
  • 15. Coordinated omission problem http://bravenewgeek.com/tag/coordinated-omission/ system.exit(0) samples 60 50 th 1 70 th 12.6 90 th 24.3 95 th 27.2 99 th 29.45 99,9 th 29.95 99,99 th 29.99 avg 8.25
  • 17. Coordinated omission problem total time 60 s max 30 s 99th 1 s
  • 18. Coordinated omission problem total time 60 s max 30 s 99th 1 s time in % for max 50%
  • 19. Coordinated omission problem total time 60 s max 30 s 99th 1 s time in % for max 50% expected time in % for 99th 50% - 1% = 49%
  • 20. Coordinated omission problem total time 60 s max 30 s 99th 1 s time in % for max 50% expected time in % for 99th 50% - 1% = 49% real time for 99th 60 s * 49% = 29.4 s
  • 22. Performance tests - why so hard? ● Coordinated omission vs Gatling http://bravenewgeek.com/tag/coordinated-omission/
  • 23. Performance tests - why so hard? ● Tests…
  • 24. Performance tests - why so hard? ● Tests…
  • 27. Why Gatling? ● non-blocking, asynchronous stack (scala, akka, netty) ● scala !!!111oneoneone (maven, sbt support) ● DSL ● recorder ● math is good ● reports ● integration & performance tests
  • 28. Gatling DSL class BasicSimulation extends Simulation { val httpConf = http .baseURL("http://computer-database.gatling.io") .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8") .acceptEncodingHeader("gzip, deflate") .acceptLanguageHeader("en-US,en;q=0.5") .userAgentHeader("Mozilla/5.0 (Macintosh; X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0") val firstScenario = scenario("First Scenario Name") .exec(http("Request name").get("/")) .pause(7) setUp(firstScenario.inject(atOnceUsers(1)).protocols(httpConf)) }
  • 29. Gatling DSL class FirstScenarioV2 extends MySimulation { val firstScenario = scenario("First Scenario Name") .exec(http("Go to root page").get("/")) .pause(7 seconds) setUp(firstScenario.inject(atOnceUsers(1)).protocols(httpConf)) }
  • 30. Gatling DSL import io.gatling.core.Predef._ import io.gatling.http.Predef._ class FirstScenarioV2 extends MySimulation { val firstScenario = scenario("First Scenario Name") .exec(http("Go to root page").get("/")) .pause(7 seconds) setUp(firstScenario.inject(atOnceUsers(1)).protocols(httpConf)) }
  • 31. Gatling DSL class ComplexScenario extends MySimulation { val complexScenario = scenario("Complex demo scenario") .exec(http("request_1").get("/")).pause(7) .exec(http("request_2").get("/computers?f=macbook")).pause(2) .exec(http("request_3").get("/computers/6")).pause(3) .exec(http("request_4").get("/")).pause(2) .exec(http("request_5").get("/computers?p=1")).pause(670 milliseconds) .exec(http("request_6").get("/computers/new")).pause(1) .exec(http("request_7") .post("/computers") .formParam("name", "MyComputer").formParam("introduced", "2012-05-30").formParam("company", "37")) setUp(complexScenario.inject(atOnceUsers(1)).protocols(httpConf)) }
  • 32. Gatling DSL class ComplexScenarioV2 extends MySimulation { val complexScenario = scenario("Complex demo scenario") .exec(goToRootPage).pause(7) .exec(searchFor("macbook")).pause(2) .exec(positionAt(6)).pause(3) .exec(goToRootPage).pause(2) .exec(goToPage(1)).pause(670 milliseconds) .exec(openNewComputerForm).pause(1) .exec(addNewComputer) setUp(complexScenario.inject(atOnceUsers(1)).protocols(httpConf)) }
  • 33. Gatling DSL class ComplexScenarioV3 extends MySimulation { val search = exec(goToRootPage).pause(7) .exec(searchFor("macbook")).pause(2) .exec(positionAt(6)).pause(3) val addComputer = exec(goToRootPage).pause(2) .exec(goToPage(1)).pause(670 milliseconds) .exec(openNewComputerForm).pause(1) .exec(addNewComputer) val complexScenario = scenario("Complex demo scenario").exec(search, addComputer) setUp(complexScenario.inject(atOnceUsers(1)).protocols(httpConf)) }
  • 34. Gatling DSL - checks scenario("DSL demo") .exec(http("go to page") .get("/computers") .check(status.is(200)) .check(status.in(200 to 210))
  • 35. Gatling DSL - checks scenario("DSL demo") .exec(http("go to page") .get("/computers") .check(regex("computers") .find(1) .exists) https://www.pinterest.com/pin/491666484294006138/
  • 36. Gatling DSL - checks scenario("First Scenario Name") .exec(http("Request name") .get("/computers") .check(jsonPath("$..foo.bar[2].baz").ofType[String].notExists) .check(xpath("//input[@id='text1']/@value").is("test")) .check(css("...").transform(_.split('|').toSeq).is(Seq("1", "2")))
  • 37. Gatling DSL - checks scenario("DSL demo") .exec(http("Authorize") .get("/auth") .check(regex("token").find(1).exists .saveAs("authorizationToken"))) .exec(http("Authorized resource") .get("/authorized_resource?token=${authorizationToken}"))
  • 38. Gatling DSL - looping repeat(5, "i") { exec(goToPage("${i}".toInt)) .pause(1) } ● repeat ● foreach ● during ● asLongAs ● forever https://blog.hubspot.com/blog/tabid/6307/bid/32019/Why-Every-Marketer-Needs-Closed-Loop-Reporting.aspx#sm.0005lrqj811waf3ntmn1cul3881gr
  • 39. Gatling DSL - polling exec( polling .every(10 seconds) .exec(searchFor("thinkpad")) ) http://www.firmus-solutions.com/terms-conditions.html
  • 40. Gatling DSL - conditions doIf(session => session("user").as[String].startsWith("admin")) { exec(goToAdminPage) } ● doIf ● doIfEquals ● doIfOrElse ● doSwitch ● doSwitchOrElse ● randomSwitch https://en.wikipedia.org/wiki/Decision_tree
  • 41. Gatling DSL - error management exec(sendMoney) .tryMax(10){ exec(checkIfMoneyReceived) } ● tryMax ● exitBlockOnFail ● exitHereIfFailed Alice Bob Kafka
  • 42. Gatling DSL - setup setUp(myScenario .inject( nothingFor(4 seconds), atOnceUsers(10), rampUsers(10) over (5 seconds)) .protocols(httpConf)) .maxDuration(10 minutes) ● constantUsersPerSec ● rampUsersPerSec ● splitUsers ● heavisideUsers
  • 43. Gatling DSL - setup setUp(myScenario .inject(atOnceUsers(10)) .protocols(httpConf)) .assertions( global.responseTime.max.lt(50), global.failedRequests.percent.is(0) ) http://englishthroughlaxas.blogspot.com/2015/07/531-24-expression-of-assertion-emphasis.html
  • 44. Gatling DSL - setup setUp(myScenario .inject(atOnceUsers(10)) .protocols(httpConf)) .throttle( reachRps(100) in (30 second), holdFor(1 minute), jumpToRps(50), holdFor(2 hours) )
  • 45. Gatling DSL - feeders val companies = List("apple", "lenovo", "hp") val feeder = Iterator.continually( Map("company" -> companies(Random.nextInt(companies.size)))) val searching = scenario("Searching") .feed(feeder) .exec(searchFor("${company}")) ● RecordSeqFeederBuilder ● CSV ● JSON ● JDBC ● Sitemap ● Redis ● … http://favim.com/orig/201104/23/Favim.com-22725.jpg
  • 46. Gatling DSL - resource inferring val httpConf = http .baseURL("http://computer-database.gatling.io") .acceptHeader("...") .acceptEncodingHeader("...") .acceptLanguageHeader("...") .inferHtmlResources() .userAgentHeader("...")
  • 47. Gatling - launching ● gatling:test ● gatling:testOnly x.y.z.GetUserSimulation
  • 48. Gatling DSL - other goodies ● Custom validators ● HTTP ○ SSL ○ SSE (Server Sent Event) ○ basic cookies support ● WebSocket ● JMS ● Pluginable architecture: ○ cassandra plugin ○ kafka plugin ○ rabbitMQ ○ AMQP
  • 49. Gatling DSL - logging/debugging ● logging ALL HTTP request and responses <logger name="io.gatling.http.ahc" level="TRACE" /> <logger name="io.gatling.http.response" level="TRACE" /> ● logging ONLY FAILED HTTP request and responses <logger name="io.gatling.http.ahc" level="DEBUG" /> <logger name="io.gatling.http.response" level="DEBUG" />
  • 50. Gatling DSL - reporting ================================================================================ ---- Global Information -------------------------------------------------------- > request count 10 (OK=10 KO=0 ) > min response time 40 (OK=40 KO=- ) > max response time 177 (OK=177 KO=- ) > mean response time 55 (OK=55 KO=- ) > std deviation 41 (OK=41 KO=- ) > response time 50th percentile 42 (OK=42 KO=- ) > response time 75th percentile 43 (OK=43 KO=- ) > response time 95th percentile 117 (OK=117 KO=- ) > response time 99th percentile 165 (OK=165 KO=- ) > mean requests/sec 0.909 (OK=0.909 KO=- ) ---- Response Time Distribution ------------------------------------------------ > t < 800 ms 10 (100%) > 800 ms < t < 1200 ms 0 ( 0%) > t > 1200 ms 0 ( 0%) > failed 0 ( 0%) ================================================================================
  • 51. Gatling DSL - distributed
  • 52. Gatling DSL - distributed IT system
  • 53. Gatling DSL - distributed ● manually ● Gatling FrontLine ● Flood.io IT system
  • 54.
  • 55. About me ➔ ➔ aludwikowski.blogspot.com ➔ github.com/aludwiko ➔ @aludwikowski