Implementation och utvärdering av trådintensiv simulator i Java

Institutionen för datavetenskap
Department of Computer and Information Science
Examensarbete
Implementation och utvärdering av trådintensiv
simulator i Java, Jetlang och Erlang
av
Henrik Holmberg
LIU-IDA/LITH-EX-G--11/010--SE
2011-05-26
Linköpings universitet
SE-581 83 Linköping, Sweden
Linköpings universitet
581 83 Linköping
Examensarbete
Implementation och utvärdering av
trådintensiv simulator i Java, Jetlang
och Erlang
av
Henrik Holmberg
LIU-IDA/LITH-EX-G--11/010--SE
2011-05-26
Handledare: Mattias Eriksson & Patrik Höglund (ENEA)
Examinator: Peter Fritzson
4
Abstract
Efficient threading is difficult to create and it is complicated to write. This
thesis describes an implementation and compares these. The evaluationof
the implementations in Java, Jetlang and Erlang reveal that Jetlang is the
best when it comes to scaling regarding complexity as well as hardware.
Erlang requires the least extra code for the threading. When many small
executions are needed, Erlang performs the best but loses the lead quickly
when the complexity increases.
5
Sammanfattning
Trådning är svårt att få så effektivt som möjligt och det är komplicerat
att skriva kod för det. Det här examensarbetet beskriver en implementation
och jämför sedan implementationerna. Utvärderingen av implementationerna i Java, Jetlang och Erlang avslöjar att Jetlang skalar bäst både när det
gäller komplexitet och hårdvara. Erlang behöver minst extra kod för att
hantera trådning. När det gäller många små körningar har även Erlang bäst
prestanda men tappar försprånget snabbt när komplexiteten ökar.
Innehåll
1 Inledning
1.1 Bakgrund . . . .
1.2 Syfte . . . . . . .
1.3 Termer . . . . . .
1.4 Relaterat arbete
1.5 Metod . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
1
1
2
3
3
2 Teori
2.1 Trådar . . . . . . . .
2.2 Objektdelning . . . .
2.3 Meddelandeskickning
2.4 Strategier . . . . . .
2.5 Testning . . . . . . .
2.5.1 Korrekthet .
2.5.2 Prestanda . .
2.6 Aktör-modellen . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5
5
6
6
6
7
7
7
8
3 Resultat
3.1 Testsystem . . . . . . . . . .
3.1.1 Prestandatest . . . . .
3.2 Implementation i Java . . . .
3.3 Implementation i Jetlang . .
3.4 Implementation i Erlang . . .
3.5 Jämförelse grundläggande . .
3.5.1 Prestanda . . . . . . .
3.5.2 Tråddrivande kod . . .
3.6 Jämförelse utökad . . . . . .
3.6.1 Prestanda . . . . . . .
3.7 Jämförelse ytterligare utökad
3.7.1 Prestanda . . . . . . .
3.8 Sammanställning . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
9
9
10
10
12
13
13
13
14
15
16
17
17
18
.
.
.
.
.
4 Diskussion
21
7
INNEHÅLL
INNEHÅLL
5 Slutsats
23
5.1 Framtida förbättringar . . . . . . . . . . . . . . . . . . . . . . 23
Litteraturförteckning
Bilagor
A
Exekvering . . . . . . . .
B
Digital kod . . . . . . . .
C
Java-implementation . . .
C.1
Grundläggande . .
C.2
Utökad . . . . . .
C.3
Ytterligare utökad
D
Jetlang-implementation .
D.1
Grundläggande . .
D.2
Utökad . . . . . .
D.3
Ytterligare utökad
E
Erlang-implementation . .
E.1
Grundläggande . .
E.2
Utökad . . . . . .
E.3
Ytterligare utökad
24
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
8
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
25
25
26
26
26
40
40
41
41
55
56
57
57
60
60
Kapitel 1
Inledning
1.1
Bakgrund
Prestandaökning av processorer sker sedan några år tillbaka inte längre genom att öka klockfrekvensen utan genom att öka antalet kärnor i processorn
för att kunna köra flera uppgifter parallellt. Sutter nämnde detta 2005 i sin
artikel (Sutter, 2005) och trenden har fortsatt på samma sätt. Det innebär
att programmerare inte kan utnyttja hela processorns kraft utan att dela
upp program i flera trådar. Program som är trådade kan utnyttja flera processorkärnor parallellt och därmed köras snabbare. Om det handlar om ett
grafiskt gränssnitt blir det mer responsivt. Det är de goda egenskaperna hos
trådade program. De sämre egenskaperna är att de är svåra att programmera och även att testa. Ordningen olika trådar kör i varierar mellan varje
körning och det finns inget bra sätt att förutspå ordningen. Därför är det
svårt att följa hur ett program körs och verifiera att det fungerar korrekt.
Det finns en del strategier för att skriva fungerande trådade program på
ett bra sätt, men det saknas strategier som alltid fungerar. Även skalbarhet
med trådade program är ett problem då det finns en kostnad när en annan
tråd ska köras.
1.2
Syfte
Syftet med det här examensarbetet är att undersöka hur bra det går att
skriva trådade program i Java med och utan Jetlang samt i Erlang. Vi
ska försöka svara på vad som skiljer implementationerna åt när det gäller
läsbarhet och prestanda.
1
1.3. TERMER
1.3
Glossary
Termer
I den här rapporten används en del termer som kan vara nya för läsaren.
Nedan listas förkorningarna som används.
Förkortning
UE
SUT
CI
JVM
Utskrivning
UserEquipment
SystemUnderTest
ControlInterface
Java Virtual Machine
Motsvarar
mobiltelefon
basstation
styrgränssnitt
kör Java
Nedan följer även en ordlista med de viktigaste begreppen.
aktör eng. actor. 8
fiber synonym för lättviktstråd. 12
förfrågan eng. request. 6, 7
genomströmning eng. throughput. 7
interfolieras eng. interleaves. 6
kanal eng. channel. 6
kapplöpningsfri eng. race-free, två trådar tävlar inte om vem som hinner
först. 8
kritiskt block eng. critical section. 6
lås eng. lock. 6, 8
meddelandesändning eng. message passing. 3, 6, 8, 21
objektdelning eng. object sharing. 6
oföränderliga eng. immutable. 6, 12
prestandamätning eng. benchmarking. 7, 8
responsivitet eng. responsiveness. 7
skalbarhet eng. scalability. 7
2
1.4. RELATERAT ARBETE
Glossary
skräphantering eng. garbage collection, samlar upp gammalt minne för
återanvändning. 7, 10
slutlig eng. final. 7, 12
svar eng. reply. 6
tillstånd eng. state. 6
ömsesidig uteslutning eng. mutual exclusion. 6
1.4
Relaterat arbete
Enligt Halén, Karlsson och Nilsson presterar Erlang mycket bättre än Java
när det gäller skapande av trådar samt meddelandesändning (eng. message
passing). Deras resultat visar att Erlang kan skapa 20 000 Erlang-processer
(Erlangs lättviktstrådar, närmaste motsvarigheten till Javas trådar) medan
Java enbart klarar upp till 1 600 trådar. När det gäller meddelandesändning
är tiden för Erlang väldigt stabil medan Java tar ungefär åtta gånger längre
tid på Microsoft Windows 95. (Halén, Karlsson & Nilsson, 1998)
Som Sun Microsystems nämner har prestandan ökat mycket sedan den
första versionen i Java HotSpot Virtual Machine (VM). Exempelvis finns
funktionalitet för att kunna eliminera kontroller vid indexering, snabbare
trådsynkronisering och omorganisering av instruktioner som inte förekommer
i optimal ordning. (Sun Microsystems, 2002)
Christopher Frantz menar att Jetlang är ett av de bästa alternativen för
introduktionen av actor-modellen i Java. Det är snabbt, enkelt och har alla
viktiga koncept. (Frantz, 2010)
1.5
Metod
Ett antal olika böcker om Java, Erlang och trådning används som litteratur
tillsammans med ett antal artiklar för att bygga upp en teoretisk grund.
Även dokumentationen för Java, Jetlang och Erlang används för att hitta
information. Med den teoretiska grunden implementeras simulatorn i Java,
Java tillsammans med Jetlang och till sist Erlang. Implementationerna skapas genom att börja med enkla fall och sedan utöka dem för att fungera med
mer avancerade fall. Med jämna mellanrum refaktoreras koden för att bli
mer strukturerad och lättläslig. Med de olika implementationerna sida vid
sida kan de jämföras i andel tråddrivande kod samt prestanda.
Enligt Bull, Smith, Westhead, Henty och Davey ska prestandamätning
vara representativ och lätt att förstå. De här egenskaperna tenderar att vara
i konflikt. För att vara så realistiskt som möjligt blir det mer komplex kod
och därför svårare att tolka resultatet. (Bull, Smith, Westhead, Henty &
Davey, 1999)
3
1.5. METOD
Glossary
Prestandamätningen i det här examensarbetet ska vara enkel för att vara enklare att tolka. Den sker med flera körningar för att ta ut ett rättvist
värde. Andel tråddrivande kod tas fram genom att räkna antalet rader som
uppfyller en given definition och dividera med totalt antal rader i filen. Definitionen som används är:
Rader som förbereder för meddelandesändning eller körning av trådar.
Denna definition innebär att de rader som måste finnas med, såsom starta
upp en tråd och skicka meddelanden, inte räknas som tråddrivande. Simulatorn utökas för att få längre körtid. Därmed blir den mer realistisk och
resultaten kan användas för att undersöka om hur väl implementationerna
skalar.
Simulator
Programmet som implementeras är en simulator av en basstation för mobiltelefonnät. För att simulera ett stort antal (tiotusentals) instanser av
mobiltelefoner (UE:ar) behöver programmet trådas. Det som sker i simulatorn är att styrgränssnittet (CI) startar upp basstationen (SUT) som sedan
körs oberoende av styrgränssnittet. Sedan startas ett antal mobiltelefoner
upp av styrgränssnittet som skickar diverse signaler, till exempel en signal
om att den har slagits på, till basstationen som svarar med en ny signal.
Figur 1.1 visualiserar detta.
SystemUnderTest
3. Power_On_Ack
2. Power_On
ControlInterface
1. Create_UE
UE
UE
UE
Figur 1.1: Översikt över simulatorn
4
Kapitel 2
Teori
2.1
Trådar
Oaks och Wong hävdar att en tråd är ett program eller del av ett program
som körs av en dator. Trådar möjliggör körning av flera program samtidigt.
Med flera trådar i ett program kan det exekvera instruktioner parallellt och
trådarna delar även minne. (Oaks & Wong, 2004, s. 11–14)
Skapandet av en ny tråd tar olika lång tid på olika plattformar enligt
Oaks och Wong, men är aldrig kostnadsfritt. Därför kan trådpoolar vara
önskvärda. De skapar ett bestämt antal trådar och återanvänder dessa för
att utföra uppgifter istället för att skapa nya för varje ny uppgift. Skillnaden
mellan att skapa nya trådar hela tiden och använda trådpooler varierar
mellan olika plattformar men det skiljer i allmänhet 100–400 mikrosekunder.
Detta kan vara relativt mycket eller lite beroende på hur lång tid uppgifterna
som körs i trådarna tar. För en uppgift som tar 5 millisekunder att slutföra
är detta en väldigt lång fördröjning men om uppgiften tar 30 sekunder är
fördröjningen försumbar. (Oaks & Wong, 2004, s. 265–266)
Goetz m. fl. säger att den optimala storleken på en trådpool är:
W
Nthreads = Ncpu · Ucpu · 1 +
C
där Nthreads är antalet trådar, Ncpu är antalet processorkärnor, Ucpu är den
önskade användningen av varje processorkärna (0 ≤ Ucpu ≤ 1) och W
C är
förhållandet mellan väntetid och exekveringstid. Väntetid är väntan på data
från exempelvis tangentbord, hårddisk eller nätverk. Denna formel tar bara
hänsyn till CPU som resurs och inte andra resurser såsom minne, databaser
och nätverk. (Goetz m. fl., 2006, s. 171)
5
2.2. OBJEKTDELNING
2.2
KAPITEL 2. TEORI
Objektdelning
Objektdelning (eng. object sharing) innebär att två eller flera objekt kan
interagera med ett objekt. Det delade objektets tillstånd (eng. state) kan
observeras och modifieras genom objektets metoder. Om objekt i flera olika trådar interagerar med objektet samtidigt, kan dessa interfolieras (eng.
interleaves) på godtyckligt sätt. Detta gör att det kan ske uppdateringar
av objektets tillstånd som inte är korrekta. För att alla tillstånd ska vara
korrekta kan ömsesidig uteslutning (eng. mutual exclusion) användas. Det
innebär att endast en tråd får vara i ett kritiskt block (eng. critical section)
eller en kritisk metod vid ett givet tillfälle. (Magee & Kramer, 2006, s. 63–73)
Enligt Goetz m. fl. finns det några olika tillvägagångssätt för att uppnå
säker objektdelning. Ett sätt är att objektdelning enbart får ske i en och
samma tråd. Objekt kan delas problemfritt mellan flera trådar om de enbart
är läsbara. Oföränderliga (eng. immutable) objekt uppfyller detta krav. Om
flera trådar måste kunna ändra på ett objekt kan det göras korrekt om
det delade objektet har intern synkronisering. Ett annat alternativ är att
använda lås (eng. locks) som en tråd måste inneha för att få tillgång till
objektet. (Goetz m. fl., 2006, s. 33–54)
2.3
Meddelandeskickning
Carver och Kuo-Chung menar att ett alternativ till delade objekt eller variabler är att istället använda sig av meddelandesändning. Detta alternativet innebär att ingen extra synkronisering behövs för att kommunicera med
andra trådar. Meddelandesändning kan vara synkron, där avsändaren väntar
tills mottagaren har tagit emot meddelandet, eller asynkron, där avsändaren
fortsätter sin exekvering så fort meddelandet skickats iväg. Begreppet kanal (eng. channel) används för de objekt som tillhandahåller sådan meddelandeskickning. Kommunikationen via kanaler sker endast åt ena hållet,
för tvåvägskommunikation finns rendezvous som använder sig av förfrågan
(eng. request) och svar (eng. reply). (Carver & Kuo-Chung, 2006, s. 258–275)
2.4
Strategier
Sandén menar att det inte finns något sätt att förhindra att en programmerare håller lås i en längre tid. För att undvika detta krävs en bra förståelse
för olika typer av synkronisering och vet vilken typ av synkronisering som
ska användas. (Sandén, 2004)
Det finns strategier för att undvika problem vid trådning. Goetz m. fl.
har sammanställt en lista över god praxis vid programmering som kräver
flera trådar. Den inkluderar följande punkter.
• Oföränderliga objekt är automatiskt trådsäkra.
6
2.5. TESTNING
KAPITEL 2. TEORI
• Gör fält konstanta (eng. final) om de inte måste kunna ändras.
• Inkapsling gör det praktiskt att hantera komplexiteten.
• Skydda varje föränderlig variabel med ett lås.
• Behåll lås tills sammansatta instruktioner är klara.
• Ett program som läser eller skriver en variabel från flera trådar utan
synkronisering är ett trasigt program.
• Inkludera trådsäkerhet i designprocessen.
• Dokumentera synkroniseringspolicyn.
(Goetz m. fl., 2006, s. 110)
2.5
2.5.1
Testning
Korrekthet
Goetz m. fl. nämner att enkla enhetstester inte skiljer sig mellan program
med en eller flera trådar i vissa fall. De testerna är helt sekventiella och allt
körs i en tråd. Testning av blockerande metoder är betydligt svårare och
kräver en timeout eller något liknande för att avgöra om en blockering har
inträffat. (Goetz m. fl., 2006, s. 248–252)
2.5.2
Prestanda
Enligt Goetz m. fl. finns det ett antal olika sätt för att göra prestandatester
på trådade program. Dessa inkluderar genomströmning (eng. throughput),
responsivitet (eng. responsiveness) och skalbarhet (eng. scalability). Genomströmning innebär hur fort ett antal parallella uppgifter blir slutförda. Responsivitet är hur lång fördröjning det är mellan att en förfrågan inkommer
till det att något görs. Skalbarhet innebär hur mycket mer genomströmning
det blir när man ökar antalet tillgängliga resurser. (Goetz m. fl., 2006, s. 247)
Prestandamätning i Java
Goetz m. fl. tar upp att det finns att antal fallgropar när det gäller prestandamätning (eng. benchmarking) i Java. Skräphantering (eng. garbage collection) i Java kan ske när som helst utan att programmeraren direkt kan
påverka den. Det går att säga till Java Virtual Machine (JVM) att den bör
köra skräphantering vid ett visst tillfälle men det finns ingenting som garanterar att så sker. Genom att lägga till en flagga till JVM skrivs det ut varje
gång skräphantering körs. När kod körs ofta, optimerar JVM koden vilket
gör att den körs snabbare när den anropas nästa gång. Detta påverkar dock
resultatet eftersom optimeringen tar tid. Även för detta går det att lägga till
7
2.6. AKTÖR-MODELLEN
KAPITEL 2. TEORI
en flagga till JVM för att få utskrifter när optimering sker. En metod som
används för att undvika optimering under prestandamätning är att värma
upp systemet först och köra igenom koden ett antal gånger. (Goetz m. fl.,
2006, s. 266–270)
Enligt Goetz optimerar även JVM bort död kod, det vill säga kod som
aldrig körs. Det innebär att det kan ta olika lång tid att köra ett program
beroende på vilken kod som exekveras under den körningen. (Goetz, 2004)
Heisenberg-principen är enligt Goetz att resultaten av prestandamätningen enbart ska visa hur lång tid en viss operation tar. Eftersom JVM optimerar kod kan brus introduceras i mätningarna och mätningarna kanske inte
skildrar det som var tänkt. (Goetz, 2005)
2.6
Aktör-modellen
Enligt Haller och Odersky är meddelandesändning i aktör (eng. actor)modellen kapplöpningsfri (eng. race-free) enligt sin design och är därför mer
säker än delat minne med lås. Meddelandesändning sker asynkront och blockerar därmed inte den som skickar. Varje aktör är oberoende av alla andra
actors. En aktör kan vara i princip vad som helst. Aktörer är lättare än
vanliga trådar och kan köras med en 1 000-faktor fler. (Haller & Odersky,
2007)
Figur 2.1: Illustration av hur aktör-modellen är utformad.
8
Kapitel 3
Resultat
3.1
Testsystem
Systemet där implementationerna körs och testas är en PC med följande
specifikationer:
• Intel Core2 Duo 6400 @ 2,13 GHz (2 kärnor)
• 3,5 GB RAM
• Microsoft Windows XP Professional Service Pack 3
• Eclipse Helios
• Java 1.6
• Jetlang 0.2.5
• Erlang 5.8.3
Tester körs även på ett system med bättre prestanda:
• Intel Core i7 950 @ 3,07 GHz (4 kärnor med HyperThreading)
• 10 GB RAM
• Ubuntu
• Java 1.6
• Jetlang 0.2.5
• Erlang 5.5.5
9
3.2. IMPLEMENTATION I JAVA
3.1.1
KAPITEL 3. RESULTAT
Prestandatest
För att få så bra mätresultat som möjligt används en uppvärmningsfas innan
mätningen påbörjas för att de flesta av optimeringarna som JVM gör ska
vara gjorda vid själva testet. Denna uppvärmningsfas fungerar helt enkelt genom att köra igenom koden som ska användas ett antal (50–100) gånger vilket Goetz m. fl. nämnde. Före varje mätning uppmanas också JVM att köra
sin skräphantering innan så det sker så lite som möjligt under mätningen.
Den skriver även ut när varje mätning börjar och slutar för att visa ifall det
sker något extra i mätningen. Även detta nämnde Goetz m. fl. i avsnittet
om testning. När antalet mobiltelefoner ökar visar det sig att skräphantering
också öka under själva mätningen vilket gör att resultaten blir mindre precisa. Förutsättningarna mellan de båda Java-implementationerna bör däremot
vara tillräckligt lika för att en jämförelse dem emellan ska vara rättvis. Detta
eftersom båda implementationerna drabbas av den ökade skräphanteringen.
Även jämförelsen med Erlang bör vara rättvis eftersom Erlang också har
skräphantering och det är den totala prestandan som är intressant. I Erlang används ett motsvarande testsystem som i Java för att det ska vara så
rättvisa resultat som möjligt.
På systemet med i7-processorn körs endast de utökade implementationerna. Java- och Jetlang-implementationerna körs genom att skapa en jar-fil
då i7-datorn saknar Java-kompilator. Erlang-implementationen kan köras på
samma sätt som på Core2 Duo-datorn.
3.2
Implementation i Java
ControlInterface
8.
1.
Dispatcher
7.
2.
5.
3.
4.
6.
SUT
UE
Figur 3.1: Implementation i Java
I figur 3.1 visas en översikt över hur simulatorn fungerar i implementationen i Java. All kommunikation mellan basstationen, mobiltelefonerna
10
3.2. IMPLEMENTATION I JAVA
KAPITEL 3. RESULTAT
och styrgränssnittet sker via Dispatcher som använder en trådpool med ett
konstant antal trådar. Dispatcher använder trådpoolen för att skicka vidare
signalerna till sin destination där de tas emot och hanteras. Så här ser ett
typiskt exekveringsflöde ut:
1. ControlInterface skickar CREATE UE till Dispatcher med en destination till mobiltelefonen.
2. Dispatcher använder en tråd ur trådpoolen för att skicka vidare signalen till mobiltelefonen där signalen hanteras och tolkas som att den
ska starta.
3. Mobiltelefonen skickar POWER ON till Dispatcher med en destination
till basstationen.
4. Dispatcher använder en tråd ur trådpoolen för att skicka vidare signalen till basstationen där signalen tolkas.
5. Basstationen skickar POWER ON ACK till Dispatcher med en destination till mobiltelefonen.
6. Dispatcher använder en tråd ur trådpoolen för att skicka vidare signalen till mobiltelefonen där signalen hanteras.
2–6 upprepas med NEGOTIATE CAPABILITIES.
7. Mobiltelefonen skickar CREATE UE ACK till Dispatcher med destination till styrgränssnittet.
8. Dispatcher använder en tråd ur trådpoolen för att skicka vidare signalen till styrgränssnittet som tar emot signalen och då vet att mobiltelefonen har startats.
Efter dessa signaler har mobiltelefonen startats och kan skicka andra signaler
som kan starta och avsluta samtal. Dessa signaler är enbart symboliska men
nedan beskrivs vad de representerar:
• CREATE UE talar om för mobiltelefonen att den ska starta.
• POWER ON talar om för basstationen att en mobiltelefon har slagits
på.
• NEGOTIATE CAPABILITIES används för att bestämma vad mobiltelefonen kan och inte kan göra.
• Alla som slutar med ACK skickas tillbaka till mobiltelefonen för att
tala om att basstationen har tagit emot signalen.
11
3.3. IMPLEMENTATION I JETLANG
KAPITEL 3. RESULTAT
Implementationen måste ha en Dispatcher eftersom en trådpool ska användas för att köra alla uppgifter på ett begränsat antal trådar. Detta eftersom
Goetz m. fl. nämnde att en trådpool är effektivare än separata trådar. Eftersom det är Java är implementationen objektorienterad och använder arv
för att modellera exempelvis olika objekt som signaler kan gå till och från
(basstation och mobiltelefon ärver från Endpoint). Varje signal är slutlig och
därmed oföränderliga vilket gör dem trådsäkra. Goetz m. fl. nämnde att ett
objekt som är slutlig automatiskt blir trådsäkert. Endast objekt som är slutliga delas mellan olika trådar och en blockerande kö används i Dispatcher
för att hantera meddelandeskickningen.
3.3
Implementation i Jetlang
ControlInterface
4.
1.
2.
UE
SUT
3.
Figur 3.2: Implementation i Java med Jetlang
I figur 3.2 visas en översikt över hur implementationen i Java tillsammans
med Jetlang fungerar. Ett exekveringsflöde ser typiskt ut som följer:
1. Styrgränssnittet skickar CREATE UE till mobiltelefonen (körs i en
egen fiber (lättviktstråd)) via en kanal som sedan hanteras av mobiltelefonen.
2. Mobiltelefonen skickar POWER ON via en kanal till basstationen där
signalen tolkas.
3. Basstationen skickar POWER ON ACK som svar i kanalen tillbaka
till mobiltelefonen där den hanteras.
2–3 upprepas med NEGOTIATE CAPABILITIES.
4. Mobiltelefonen skickar CREATE UE ACK som svar i kanalen till styrgränssnittet.
12
3.4. IMPLEMENTATION I ERLANG
KAPITEL 3. RESULTAT
Efter detta är mobiltelefonen startad och kan skicka signaler som kan starta
och avsluta samtal. Implementationen startar allt i fibrer som är lättviktstrådar
vilka tillhandahålls av Jetlang. Fibrerna körs i en trådpool som Jetlang hanterar. Därför kan varje objekt som kräver en oberoende exekvering startas
upp i en egen fiber och använda kanaler som tillåter tvåvägskommunikation
(rendezvous). Detta nämner Magee och Kramer som en variant av meddelandeskickning.
3.4
Implementation i Erlang
Implementationen i Erlang stämmer överens med figur 3.2 och fungerar
på samma sätt som implementationen i Jetlang. Alla mobiltelefoner samt
basstationen körs i var sin Erlang-process. De skickar meddelanden med Erlangs inbyggda funktioner. Dessa kanaler stämmer överens med det Magee
och Kramer nämnde.
3.5
Jämförelse grundläggande
Jämförelsen sker med avseende på effektivitet där måttet är hur många
mobiltelefoner som hinner skapas på en sekund och hur stor del av koden som
går åt till att hantera trådningen. Dessa två mått är kvantitativa och lätta
att mäta. Utöver dessa kan även läsbarhet tas med som är ett kvalitativt
mått och mycket subjektivt. Därmed är det svårt att använda detta på ett
rättvist sätt.
3.5.1
Prestanda
Figur 3.3 visar resultatet av körningar med olika antal trådar. Värdet som
användes är medianen av 20 körningar efter varandra med 10 000 mobiltelefoner. Det minsta antalet trådar som kan användas i Java-implementationen
är tre eftersom Dispatcher och basstationen behöver var sin tråd. I Jetlang
krävs minst två för att driva trådpoolen. Resultatet visar att prestandan
är bland det bästa vid 4–6 trådar. Efter det går det lite upp och ner men
prestandan blir inte bättre. Detta stämmer överens med formeln Goetz m.
fl. nämnde.
En jämförelse av hur många mobiltelefoner implementationerna klarar
av under 700 ms visas i figur 3.4. Enbart Java börjar på en sämre tid och
tiden det tar att exekvera ökar lite snabbare än med Jetlang. Av resultatet
framgår att med Jetlang går det i genomsnitt 30 % snabbare än enbart Java.
I Erlang-implementationen går det betydligt snabbare än i Java med
eller utan Jetlang. Detta visas i figur 3.6. Den klarar av att köra många
fler mobiltelefoner på samma tid och är bättre på att utnyttja processorn. Implementationen i Java och Jetlang ligger på 50–85 % av processorns kapacitet. Erlang-implementationen ligger strax under 100 %. Dessa
13
3.5. JÄMFÖRELSE GRUNDLÄGGANDE
KAPITEL 3. RESULTAT
Figur 3.3: Resultat av prestandatest med olika antal trådar i Java och Jetlang.
värden är tagna genom observation av aktivitetshanteraren i Windows under testkörningarna. Vid utökande av simulatorn med fler signaler och även
att en mobiltelefon blir tvungen att skicka om en signal, blir prestandan i
Erlang mycket sämre. Processoranvändningen ner till 50 % för Erlang när
fler än 1 300 000 mobiltelefoner körs i denna utökade simulator.
3.5.2
Tråddrivande kod
För att mäta hur mycket av koden som går åt till att hantera trådningen i implementationerna används ett procentuellt mått på de filerna som påverkas.
Måttet innebär att de rader som krävs för att starta eller stoppa trådar,
synkronisering, initiera objekt för trådhantering räknas som rader för att
hantera trådning. Dock är send och receive ej inräknade i detta då de alltid
måste vara med. Det gav följande resultat för Java-implementationen:
• ControlInterface: 19 %
• Dispatcher: 100 %
• SystemUnderTest: 22 %
• UserEquipment: 2 %
Resten av filerna påverkas inte och är därför inte med. På hela koden innebär
det att ungefär 130 rader går åt till att hantera trådningen. Dispatcher
14
3.6. JÄMFÖRELSE UTÖKAD
KAPITEL 3. RESULTAT
Figur 3.4: Maximalt antal mobiltelefoner (UE) i Java
finns enbart för att hantera trådning och behövs enbart för att köra flera
mobiltelefoner på samma tråd.
För Java med Jetlang gav det följade resultat:
• ControlInterface: 22 %
• UserEquipment: 15 %
Denna implementationen kräver trådrelaterad kod i enbart två av filerna
och ungefär 70 rader går åt totalt.
I Erlang är den totala mängden kod mycket mindre och det kan därför
vara svårare att jämföra med Java-implementationerna. Måttet gav följande
resultat:
• control interface: 21 %
• system under test: 5 %
• user equipment: 3 %
Totalt är det 10 rader som passar in på definitionen av måttet.
3.6
Jämförelse utökad
Den utökade simulatorn skickar följande signaler utöver de i den grundläggande:
15
3.6. JÄMFÖRELSE UTÖKAD
KAPITEL 3. RESULTAT
Figur 3.5: Maximalt antal mobiltelefoner (UE) i Jetlang
• CALL SETUP, kopplar upp ett nytt samtal.
• CALL SETUP ACK
• CALL END, avslutar ett samtal.
• CALL END ACK
En mobiltelefon kan dessutom få REJECT från basstationen vilket innebär
att den måste skicka om signalen.
3.6.1
Prestanda
Som figur 3.7 visar ökade prestandan kraftigt när implementationen kördes
på en kraftfullare maskin. Denna maskin hade många komponenter som
var bättre än den första maskinen vilket innebär att siffrorna inte skildrar
hur väl det skalar givet en viss komponent. Även operativsystemen är olika
vilket gör att det endast går att jämföra resultaten på samma system med
varandra. Figur 3.8 visar att Jetlang ökade ännu mer. Erlang ökade minst
enligt figur 3.9.
Erlang körde i snitt 3,0 gånger snabbare än Java på Core2 Duo. På Core
i7 körde Erlang bara 1,2 gånger snabbare. Jämfört med Jetlang körde Erlang
2,3 gånger snabbare på Core2 Duo, men på Core i7 var Jetlang 2,6 gånger
snabbare än Erlang.
16
3.7. JÄMFÖRELSE YTTERLIGARE UTÖKAD
KAPITEL 3. RESULTAT
Figur 3.6: Maximalt antal mobiltelefoner (UE) i Erlang
I den grundläggande implementationen är Erlang effektivast följd Jetlang. Detta fortsätter även upp till de andra implementationerna men Erlangs försprång minskar rejält.
Sett till minnesanvändning använder Erlang ungefär 60 MB vid körning
av 50 000 mobiltelefoner. Java använder ungefär lika mycket som Erlang.
Även Jetlang ligger kring samma värden. Dessa värden är tagna genom
observation av aktivitetshanteraren i Windows.
3.7
Jämförelse ytterligare utökad
Den ytterligare utökade simulatorn skickar följande signaler utöver de i den
utökade:
• DATA SETUP, startar dataöverföring.
• DATA SETUP ACK
• DATA END, avslutar dataöverföring.
• DATA END ACK
3.7.1
Prestanda
I den ytterligare utökade simulatorn är Erlang 3,1 gånger snabbare än Java
och 1,9 gånger snabbare än Jetlang. Det innebär att Erlang är ungefär lika
17
3.8. SAMMANSTÄLLNING
KAPITEL 3. RESULTAT
Figur 3.7: Maximalt antal mobiltelefoner (UE) i Java, Core2 Duo jämfört
med i7.
mycket snabbare än Java som med den utökade simulatorn. Erlang är dock
inte lika mycket snabbare än Jetlang.
3.8
Sammanställning
Figur 3.10 visar en sammanställning över testresultaten av den utökade
simulatorn. Java på Core2 Duo är långsammast följt av Jetlang. Erlang
är snabbast på Core2 Duo men är långsammare än Jetlang på Core i7. Java
är långsammast även på Core i7.
Tabell 3.1 visar en sammanställning över hur olika implementationer
presterar i förhållande till varandra på Core2 Duo. 1, 2 och 3 i tabellen motImpl
Java1
Java2
Java3
Jet1
Jet2
Jet3
Erl1
Erl2
Erl3
Java1
100 %
116 %
166 %
84 %
100 %
114 %
21 %
44 %
56 %
Java2
86 %
100 %
143 %
73 %
86 %
99 %
19 %
38 %
49 %
Java3
60 %
70 %
100 %
51 %
60 %
69 %
13 %
27 %
34 %
Jet1
119 %
138 %
197 %
100 %
119 %
136 %
26 %
53 %
67 %
Jet2
100 %
115 %
166 %
84 %
100 %
114 %
21 %
45 %
56 %
Jet3
88 %
102 %
146 %
74 %
88 %
100 %
19 %
39 %
49 %
Erl1
466 %
540 %
773 %
392 %
465 %
531 %
100 %
207 %
262 %
Erl2
225 %
260 %
373 %
189 %
225 %
256 %
48 %
100 %
126 %
Erl3
178 %
206 %
295 %
150 %
178 %
203 %
38 %
79 %
100 %
Tabell 3.1: Sammanställning av relativ prestanda vid 20 000 mobiltelefoner.
18
3.8. SAMMANSTÄLLNING
KAPITEL 3. RESULTAT
Figur 3.8: Maximalt antal mobiltelefoner (UE) i Jetlang, Core2 Duo jämfört
med i7.
svarar grundläggande, utökad respektive ytterligare utökad. För att se hur
snabb en implementation är i förhållande till en annan, följ raden som ska
jämföras till kolumnen den ska jämföras med. 50 % innebär att implementationen på raden är dubbelt så snabb som implementationen på kolumnen.
19
3.8. SAMMANSTÄLLNING
KAPITEL 3. RESULTAT
Figur 3.9: Maximalt antal mobiltelefoner (UE) i Erlang, Core2 Duo jämfört
med i7.
Figur 3.10: Tid för att köra 20 000 mobiltelefoner (UE) på Core2 Duo samt
Core i7.
20
Kapitel 4
Diskussion
Anledningen till att det för varje mätvärde togs medianen av 20 körningar är
för att vid observation verkade de flesta stämma väl överens. En del avvek
men med medianen påverkas inte det slutliga mätvärdet. Det var också
opraktiskt att ha för många körningar då varje prestandamätning skulle ta
väldigt lång tid.
Måttet för att mäta hur mycket av koden som går åt till att hantera
trådning ska tas med en nypa salt. Det är ett ungefärligt mått och inte helt
objektivt. Däremot bör det ge en uppfattning om hur väl konstruerat ett
språk är för att hantera trådning. Det kan även nämnas att måttet inte
tar med alla rader som tillkommer indirekt av att trådningen måste skötas
manuellt, till exempel att avsändare samt mottagare måste finnas med i
Signal i den naiva implementationen men inte i de andra två. Även blankrader påverkar måttet då det totala antalet rader används. Fler blankrader
innebär mindre andel som går åt till trådning.
Personligen tycker jag att Erlang-implementationen är mest lättläst eftersom det är mycket mindre kod och har inbyggd stöd för lättviktstrådar
samt meddelandesändning. Efter det kommer implementationen i Jetlang
eftersom signalerna inte behöver gå genom en Dispatcher där. I Erlang blir
det dessutom ett rakare exekveringsflöde i varje mobiltelefon och inte så
mycket hopp i koden. Med Jetlang blir det rakare än i enbart Java men det
blir mer hopp i koden vilket gör det mindre läsbart.
Anledningen till att den utökade Erlang-implementationen inte alls presterar lika bra som den grundläggande är förmodligen att det tar längre tid
att köra varje Erlang-process och därmed hinner Erlang inte med. För att orka med ökat antal som tar längre tid att köra måste resurserna ökas. Denna
teori stärks av att med den ytterligare utökade implementationen skalar den
ännu sämre. Det verkar som att Erlang är bäst lämpat för många, mycket
korta processer.
I mätresultatet blir det väldigt konstiga siffror mot slutet. Det beror på
att vissa av körningarna inte hinner köra klart på under en sekund. De sista
21
KAPITEL 4. DISKUSSION
värde i mätningarna varierar väldigt mycket och är inte pålitliga. Därför
valde jag valde att sätta gränsen vid 700 ms även om mätningar togs upp
till 1000 ms för att inte få med de opålitliga siffrorna i diagrammen. Detta
bör inte ha några implikationer på det slutsatsen.
När det gäller resultaten av Jetlang-körningen på i7-datorn blev värden
väldigt underliga i förhållande till de andra körningarna. De andra körningarna gav ganska raka linjer medan den här körningen gav väldigt varierande
värden när antalet mobiltelefoner steg. Vad det beror på kan jag inte riktigt
förklara men jag körde testerna flera gånger med samma resultat. Det kan
vara att det är en äldre version av Erlang på i7-datorn och därmed presterar
den inte lika bra.
22
Kapitel 5
Slutsats
Erlang har mycket bättre prestanda än Java med eller utan Jetlang i den
grundläggande simulatorn. Samma antal mobiltelefoner körs på 5,3 gånger
snabbare tid jämfört med Java. Med den utökade simulatorn kör den bara 3,0
gånger snabbare. Andel tråddrivande kod är högst i Java-implementationen
och betydligt lägre i Jetlang-implementationen. Allra lägst är den i Erlangimplementationen. Exekveringsflödet för en mobiltelefon är mycket enkelt
att följa i Erlang-implementationen med inga hopp i koden. Jetlang-implementationen är inte lika lätt att följa men är enklare än Java-implementationen.
Testresultaten blir bättre när de körs på ett kraftfullare system men
skalar olika bra. Erlang skalar inte lika bra när komplexiteten i implementationen ökar och inte heller när hårdvaran uppgraderas. Jetlang är därmed
ett bättre val vid behov av trådning med hög komplexitet och höga prestandakrav. Erlang är däremot överlägset lättast när det gäller hur lite
tråddrivande kod som behövs.
5.1
Framtida förbättringar
För att utforska området ytterligare kan implementationerna utökas ytterligare för att simulera mer komplexa system och på så vis få mer precisa
resultat. Testerna kan även förbättras genom att köras under en längre period för att andra faktorer ska spela så liten roll som möjligt. Det vore även
intressant att se om det fortsätter skala på samma vis när testsystemets
prestanda ökar ytterligare. Ytterligare tester med Jetlang vore intressant
för att se om det fortsätter prestera lika bra.
23
Litteraturförteckning
Bull, J. M., Smith, L. A., Westhead, M. D., Henty, D. S. & Davey, R. A.
(1999). A benchmark suite for high performance java. I Proceedings
of acm 1999 java grande conference (s. 81–88). ACM Press.
Carver, R. H. & Kuo-Chung, T.
(2006).
Modern Multithreading :
Implementing, Testing, and Debugging Multithreaded Java and
C++/Pthreads/Win32 Programs. Hoboken, New Jersey, USA: Wiley.
Frantz, C. (2010). Overview of Java based Message passing frameworks.
Goetz, B.
(2004, december).
Java theory and practice:
Dynamic
compilation
and
performance
measurement.
http://www.ibm.com/developerworks/java/library/j-jtp12214/.
Goetz,
B.
(2005,
februari).
Java
theory
and
practice:
Anatomy
of
a
flawed
microbenchmark.
http://www.ibm.com/developerworks/java/library/j-jtp02225/.
Goetz, B., Peierls, T., Bloch, J., Bowbeer, J., Holmes, D. & Lea, D.
(2006). Java Concurrency in Practice. Stouchton, Massachusetts,
USA: Addison-Wesley.
Haller, P. & Odersky, M. (2007). Actors That Unify Threads and
Events (forskningsrapport). École Polytechnique Fédérale de Lausanne (EPFL), 1015 Lausanne, Switzerland: Programming Methods Lab
(LAMP).
Halén, J., Karlsson, R. & Nilsson, M. (1998). Performance Measurements
of Threads in Java and Processes in Erlang (forskningsrapport). Ericsson. http://www.sics.se/ joe/ericsson/du98024.html.
Magee, J. & Kramer, J. (2006). Concurrency : State Models & Java Programs. Chichester, West Sussex, England: Wiley.
Oaks, S. & Wong, H. (2004). Java Threads. Sebastopol, California, USA:
O’Reilly.
Sandén, B. (2004, april). Coping with Java Threads. Computer , 37 (4),
20–27.
Sun Microsystems. (2002, september). The Java HotSpot Virtual Machine, v1.4.1. http://java.sun.com/products/hotspot/docs/whitepaper/
Java Hotspot v1.4.1/Java HSpot WP v1.4.1 1002 1.html.
Sutter, H. (2005, mars). The Free Lunch Is Over. Dr. Dobb’s Journal ,
30 (3).
24
Bilagor
A
Exekvering
För att kunna kompilera och köra Java-implementationen krävs att Java
1.6 är installerat på datorn. Simulatorn tar antalet mobiltelefoner (UE) som
första argument och antalet trådar som ett eventuellt andra argument. För
att köra testerna krävs JUnit.
Kraven för Jetlang-implementationen är samma som för enbart Java men
kräver också att Jetlang-biblioteket är tillagt.
Vid enstaka körning av simulatorn rekommenderas att använda följande
VM argument:
-Djava.util.logging.config.file="logging.properties"
För att köra testerna rekommenderas följande VM argument:
-verbose:gc
-XX:+PrintCompilation
-Djava.util.logging.config.file=
"src\com\enea\simulator\tests\logging.properties"
Det första argumentet gör så att VM skriver ut varje gång den kör skräphantering.
Det andra argumentet gör så VM skriver ut när kompilering och optimering
sker. Det sista är för att använda en speciell logginställning som orsakar
mindre utskrifter.
Erlang-implementationen kräver att Erlang är installerat. Kompilering
sker med följande kommando i kommandoraden.
erl -make
Simulatorn startas med följande kommando i Erlang-interpretatorn.
control_interface:run(N).
N är antalet mobiltelefoner (UE). För att köra testet körs med följande
kommando.
max_ue:start().
25
B. DIGITAL KOD
B
Litteraturförteckning
Digital kod
All källkod finns tillgänglig att ladda ner i zip-format på följande webbplats.
http://www-und.ida.liu.se/~henho106/exjobb/
Där finns det paket för enskilda versioner och ett stort paket. Det följer
också med instruktioner för hur man ska köra dem.
C
C.1
Java-implementation
Grundläggande
package com.enea.simulator;
import
import
import
import
java.util.concurrent.ExecutorService;
java.util.concurrent.Executors;
java.util.concurrent.TimeUnit;
java.util.logging.Logger;
import
import
import
import
import
import
import
import
import
import
com.enea.simulator.behavior.Behavior;
com.enea.simulator.behavior.DefaultBehavior;
com.enea.simulator.behavior.NoDelayBehavior;
com.enea.simulator.behavior.TestBehavior;
com.enea.simulator.endpoint.Endpoint;
com.enea.simulator.endpoint.SystemUnderTest;
com.enea.simulator.endpoint.UserEquipment;
com.enea.simulator.signal.Signal;
com.enea.simulator.signal.SignalType;
com.enea.simulator.signal.order.DefaultSignalOrder;
public class ControlInterface implements Endpoint {
public static boolean test = true;
public static void main(String[] args) throws InterruptedException {
// Get number of UserEquipments to use from arguments
if (args.length < 1) {
System.err.println("Please specify number of UserEquipments");
return;
}
int nrOfUEs = Integer.parseInt(args[0]);
if (nrOfUEs < 1) {
System.err
.println("Please specify a positive number of UserEquipments");
return;
}
ControlInterface controlInterface = new ControlInterface(3000);
int numberOfThreads = 0;
// Get optional argument for number of threads
if (args.length > 1) {
int nrThreadsTemp = Integer.parseInt(args[1]);
if (nrThreadsTemp > 0) {
numberOfThreads = nrThreadsTemp;
}
}
// Start the control interface and catch unexpected exceptions.
try {
controlInterface.runControlInterface(nrOfUEs, numberOfThreads);
26
} catch (Exception e) {
System.err.println(e.getMessage());
e.printStackTrace();
}
}
// Non−static
// ////////////////////////////////////////////////////////////////////
private
private
private
private
private
private
Logger log;
int waitTime;
long startTime;
long stopTime;
int createdUEs;
int targetUEs;
public ControlInterface(int waitTime) {
startTime = System.nanoTime();
this.waitTime = waitTime;
log = Logger.getLogger(this.getClass().getName());
}
@Override
public void handleSignal(final Signal signal) {
try {
if (signal.getSignalType() == SignalType.CREATE_UE_ACK) {
Logger.getLogger(this.getClass().getName()).info(
"ControlInterface: got CREATE_UE_ACK");
synchronized (this) {
createdUEs++;
stopTime = System.nanoTime();
}
}
} catch (Exception e) {
System.err.println(e.getMessage());
e.printStackTrace();
}
}
public void runControlInterface(int numberOfUEs)
throws InterruptedException {
runControlInterface(numberOfUEs, 0);
}
public boolean runControlInterface(final int numberOfUEs, int numberOfThreads)
throws InterruptedException {
targetUEs = numberOfUEs;
ExecutorService threadPool = createUeThreadPool(numberOfThreads);
final Dispatcher dispatcher = new Dispatcher(threadPool);
Thread dispatcherThread = new Thread(dispatcher);
dispatcherThread.start();
final SystemUnderTest sut;
if (test) {
sut = new SystemUnderTest(dispatcher);
} else {
sut = new SystemUnderTest(dispatcher);
}
Thread sutThread = new Thread(sut);
sutThread.start();
Thread createUeThread = new Thread(new Runnable() {
@Override
public void run() {
createUes(numberOfUEs, sut, dispatcher);
}
});
createUeThread.start();
Thread.sleep(waitTime);
log.warning("Interrupting");
threadPool.shutdown();
createUeThread.interrupt();
dispatcherThread.interrupt();
sutThread.interrupt();
Thread.sleep(10);
threadPool.shutdownNow();
boolean result = checkAllCreated();
log.warning("" + getRunTime(TimeUnit.MILLISECONDS));
return result;
}
private boolean checkAllCreated() {
boolean success;
synchronized (this) {
success = createdUEs == targetUEs;
}
if (success) {
log.warning("Got all CREATE_UE_ACK.");
} else {
log.warning("Did not get all CREATE_UE_ACK.");
}
return success;
}
private void createUes(int nrOfUEs, SystemUnderTest sut,
Dispatcher dispatcher) {
for (int i = 0; i < nrOfUEs; i++) {
Behavior behavior = null;
switch (i % 2) {
case 0:
behavior = new NoDelayBehavior();
break;
case 1:
behavior = new DefaultBehavior();
break;
default:
throw new RuntimeException(
"Should not happen, BehaviorType in ControlInterface.");
}
if (test) {
behavior = new TestBehavior();
}
UserEquipment ue = new UserEquipment(sut, dispatcher, i, behavior, new DefaultSignalOrder());
dispatcher.dispatch(new Signal(this, ue, SignalType.CREATE_UE));
}
}
private ExecutorService createUeThreadPool(int numberOfThreads) {
int nrOfCPU = Runtime.getRuntime().availableProcessors();
int utilization = 1;
double wc = 2.0;
int nrOfThreads = (int) ((double) nrOfCPU * (double) utilization * (1.0 + wc));
// Override if user specified number of threads
if (numberOfThreads > 2) {
nrOfThreads = numberOfThreads − 2;
}
log.warning("Creating " + nrOfThreads + " threads on " + nrOfCPU
+ " CPUs.");
ExecutorService threadPool = Executors.newFixedThreadPool(nrOfThreads);
return threadPool;
}
public long getRunTime(TimeUnit unit) {
return unit.convert(stopTime − startTime, TimeUnit.NANOSECONDS);
}
}
package com.enea.simulator;
import
import
import
import
import
java.util.concurrent.BlockingQueue;
java.util.concurrent.ExecutorService;
java.util.concurrent.LinkedBlockingQueue;
java.util.concurrent.RejectedExecutionException;
java.util.logging.Logger;
import com.enea.simulator.signal.Signal;
public class Dispatcher implements Runnable {
private final ExecutorService threadPool;
private final BlockingQueue<Signal> signalQueue = new LinkedBlockingQueue<Signal>();
private Logger log;
public Dispatcher(ExecutorService threadPool) {
this.threadPool = threadPool;
log = Logger.getLogger(ControlInterface.class.getName());
}
/**
* Sends a signal when the time set in Signal has past.
*/
@ThreadSafe
public void dispatch(final Signal signal) {
signalQueue.add(signal);
}
@Override
public void run() {
try {
handleSignals();
} catch (InterruptedException ignored) {
log.info("Dispatcher exiting.");
} catch (Exception e) {
log.severe("Dispatcher: " + e.getMessage());
}
}
/**
* Only to be called once to run in separate thread. Sends signals from the
* queue when the delay has run out.
*/
public void handleSignals() throws InterruptedException {
while (true) {
final Signal signal = signalQueue.take();
// Keep trying until task is executed.
boolean notExecuted = true;
while (notExecuted) {
try {
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
signal.getReceiver().handleSignal(signal);
} catch (Exception e) {
System.err.println(e.getMessage());
e.printStackTrace();
}
}
});
notExecuted = false;
} catch (RejectedExecutionException ignored) {
Thread.sleep(10);
}
}
}
}
}
package com.enea.simulator;
import java.util.logging.ConsoleHandler;
public class SimulatorConsoleHandler extends ConsoleHandler {
public SimulatorConsoleHandler() {
super();
this.setOutputStream(System.out);
}
}
package com.enea.simulator;
/**
* This class or method is thread safe and can be called without synchronization
* across threads.
*/
public @interface ThreadSafe {
}
package com.enea.simulator.behavior;
import com.enea.simulator.signal.SignalType;
public interface Behavior {
public void executeCalculations(SignalType signalType);
}
package com.enea.simulator.behavior;
import
import
import
import
java.io.BufferedWriter;
java.io.File;
java.io.FileWriter;
java.util.Random;
import com.enea.simulator.ThreadSafe;
import com.enea.simulator.signal.SignalType;
@ThreadSafe
public class DefaultBehavior implements Behavior{
private Random generator = new Random();
@Override
public void executeCalculations(SignalType signalType) {
if (signalType == SignalType.CALL_SETUP) {
writeAndDeleteFile(10000);
} else {
writeAndDeleteFile(1000);
}
}
private void writeAndDeleteFile(int rounds) {
try {
int number = generator.nextInt(100000);
FileWriter fstream = new FileWriter("tmpFile" + number + ".txt");
BufferedWriter out = new BufferedWriter(fstream);
for (int i = 0; i < rounds; i++) {
out.write("Dummy data123456");
}
out.close();
File file = new File("tmpFile" + number + ".txt");
file.delete();
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
}
}
}
package com.enea.simulator.behavior;
import com.enea.simulator.signal.SignalType;
public class NoDelayBehavior implements Behavior {
@Override
public void executeCalculations(SignalType signalType) {
}
}
package com.enea.simulator.behavior;
import com.enea.simulator.signal.SignalType;
public class TestBehavior implements Behavior {
@Override
public void executeCalculations(SignalType signalType) {
}
}
package com.enea.simulator.endpoint;
import com.enea.simulator.ThreadSafe;
import com.enea.simulator.signal.Signal;
public interface Endpoint {
/**
* Thread−safe if the class implementing it does not access non−thread−safe
* methods.
*/
@ThreadSafe
public void handleSignal(final Signal signal);
}
package com.enea.simulator.endpoint;
import
import
import
import
java.util.HashMap;
java.util.concurrent.BlockingQueue;
java.util.concurrent.LinkedBlockingQueue;
java.util.logging.Logger;
import
import
import
import
com.enea.simulator.Dispatcher;
com.enea.simulator.ThreadSafe;
com.enea.simulator.signal.Signal;
com.enea.simulator.signal.SignalType;
public class SystemUnderTest implements Endpoint, Runnable {
private final BlockingQueue<Signal> signalQueue = new LinkedBlockingQueue<Signal>();
private final Dispatcher dispatcher;
private final HashMap<SignalType, SignalType> responseSignalTypes = new HashMap<SignalType, SignalType>();
private Logger log;
public SystemUnderTest(Dispatcher dispatcher) {
this.dispatcher = dispatcher;
log = Logger.getLogger(this.getClass().getName());
responseSignalTypes.put(SignalType.POWER_ON, SignalType.POWER_ON_ACK);
responseSignalTypes.put(SignalType.NEGOTIATE_CAPABILITIES, SignalType.NEGOTIATE_CAPABILITIES_ACK);
responseSignalTypes.put(SignalType.CALL_SETUP, SignalType.CALL_SETUP_ACK);
responseSignalTypes.put(SignalType.CALL_END, SignalType.CALL_END_ACK);
}
/**
* For testing purposes.
*/
public BlockingQueue<Signal> getSignalQueue() {
return signalQueue;
}
/**
* Do not call this from other threads. Use receiveSignal instead.
*/
@Override
public void handleSignal(final Signal signal) {
log.info("Processing signal: " + signal.toString());
final SignalType signalType = responseSignalTypes.get(signal.getSignalType());
if (signalType == null) {
log.severe("Unknown signal " + signal);
throw new IllegalArgumentException("Unknown signal " + signal);
}
final Signal result = new Signal(this, signal.getSender(), signalType);
dispatcher.dispatch(result);
}
/**
* Takes a signal and adds it to the signal queue and
* immediately returns.
*/
@ThreadSafe
public void receiveSignal(final Signal signal) {
signalQueue.add(signal);
}
@Override
public void run() {
try {
handleSignals();
} catch (InterruptedException ignored) {
log.info("System under test is exiting");
} catch (IllegalArgumentException e) {
log.severe("SUT: " + e.getMessage());
}
}
/**
* Keeps processing all the signals in the queue.
*/
private void handleSignals() throws InterruptedException {
while (true) {
handleSignal(signalQueue.take());
}
}
}
package com.enea.simulator.endpoint;
import java.util.logging.Logger;
import
import
import
import
import
import
com.enea.simulator.ControlInterface;
com.enea.simulator.Dispatcher;
com.enea.simulator.behavior.Behavior;
com.enea.simulator.signal.Signal;
com.enea.simulator.signal.SignalType;
com.enea.simulator.signal.order.SignalOrder;
public class UserEquipment implements Endpoint {
private
private
private
private
private
private
private
private
final int id;
final SystemUnderTest sut;
SignalType expectedSignalType = SignalType.CREATE_UE;
final Dispatcher dispatcher;
ControlInterface controlInterface;
final Logger log;
final Behavior behavior;
final SignalOrder signalOrder;
public UserEquipment(SystemUnderTest sut, Dispatcher dispatcher, int id,
Behavior behavior, SignalOrder signalOrder) {
this.id = id;
this.sut = sut;
this.dispatcher = dispatcher;
this.behavior = behavior;
this.signalOrder = signalOrder;
log = Logger.getLogger(this.getClass().getName());
}
public int getId() {
return id;
}
@Override
public void handleSignal(final Signal signal) {
SignalType signalType = signal.getSignalType();
if (signalType != expectedSignalType) {
throw new IllegalStateException("Got unexpected signal: "
+ signalType + " Waiting for: " + expectedSignalType);
}
log.info(createMessage(signalType.toString()));
if (signalType == SignalType.CREATE_UE) {
controlInterface = (ControlInterface) signal.getSender();
}
SignalType returnSignalType = signalOrder.getNextSignalType(signal.getSignalType());
if (returnSignalType != SignalType.NONE) {
expectedSignalType = signalOrder.getNextSignalType(returnSignalType);
if (returnSignalType == SignalType.CREATE_UE_ACK) {
behavior.executeCalculations(returnSignalType);
// Send ack to CI
dispatcher.dispatch(new Signal(this, controlInterface,
returnSignalType));
// Send next signal to SUT
returnSignalType = signalOrder.getNextSignalType(returnSignalType);
expectedSignalType = signalOrder.getNextSignalType(returnSignalType);
behavior.executeCalculations(returnSignalType);
dispatcher.dispatch(new Signal(this, sut, returnSignalType));
} else {
behavior.executeCalculations(returnSignalType);
dispatcher.dispatch(new Signal(this, sut, returnSignalType));
}
} else {
expectedSignalType = SignalType.NONE;
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("ID: ");
sb.append(id);
return sb.toString();
}
private String createMessage(final String message) {
StringBuilder sb = new StringBuilder();
sb.append("UE ");
sb.append(id);
sb.append(": ");
sb.append(message);
sb.append(".");
return sb.toString();
}
}
package com.enea.simulator.signal;
import com.enea.simulator.ThreadSafe;
import com.enea.simulator.endpoint.Endpoint;
/**
* This class represents an immutable signal with a sender, a receiver, a signal
* type and optional delay.
*
* @author helm
*
*/
@ThreadSafe
public class Signal {
private final Endpoint receiver;
private final Endpoint sender;
private final SignalType signal;
public Signal(final Endpoint sender, final Endpoint receiver,
final SignalType signal) {
this.sender = sender;
this.signal = signal;
this.receiver = receiver;
}
public Endpoint getReceiver() {
return receiver;
}
public Endpoint getSender() {
return sender;
}
public SignalType getSignalType() {
return signal;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Sender ");
sb.append(sender.toString());
sb.append("; Signal: ");
sb.append(signal.toString());
return sb.toString();
}
}
package com.enea.simulator.signal;
public enum SignalType {
POWER_ON, POWER_ON_ACK,
NEGOTIATE_CAPABILITIES, NEGOTIATE_CAPABILITIES_ACK,
CREATE_UE, CREATE_UE_ACK,
CALL_SETUP, CALL_SETUP_ACK,
CALL_END, CALL_END_ACK,
NONE
}
package com.enea.simulator.tests;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.enea.simulator.ControlInterface;
public class PerformanceNoDelayTest {
private static final int WARMUP_WAIT_TIME = 25;
private static final int WAIT_TIME = 2000;
private static final int ROUNDS = 20;
private static final int NUMBER_OF_UES = 10000;
private static final int MAX_THREADS = 15;
private ControlInterface controlInterface;
@Before
public void setUp() throws Exception {
warmup();
}
private void warmup() {
System.out.println("Warming up...");
ControlInterface.test = true;
for (int i = 0; i < 50; i++) {
controlInterface = new ControlInterface(WARMUP_WAIT_TIME);
try {
controlInterface.runControlInterface(5, 3);
} catch (InterruptedException ignored) {
System.err.println("Warmup interrupted");
}
}
for (int i = 0; i < 5; i++) {
controlInterface = new ControlInterface(WARMUP_WAIT_TIME);
try {
controlInterface.runControlInterface(NUMBER_OF_UES, MAX_THREADS);
} catch (InterruptedException ignored) {
System.err.println("Warmup interrupted");
}
}
System.gc();
System.runFinalization();
System.out.println("Done warming up.");
}
@After
public void tearDown() throws Exception {
}
@Test
public void testRunControlInterface() {
long[] results = new long[MAX_THREADS];
for (int i = 2; i < MAX_THREADS; i++) {
try {
results[i] = testNumberOfThreads(i + 1);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
for (int i = 2; i < MAX_THREADS; i++) {
System.out.println((i + 1) + "\t" + (results[i] / (1000 * 1000)));
}
System.out.println("Performance test done.");
}
private long testRoundsForThreads(int numberOfThreads)
throws InterruptedException {
controlInterface = new ControlInterface(WAIT_TIME);
System.gc();
System.runFinalization();
Thread.sleep(50);
System.out
.println("Entering −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−");
try {
boolean success = controlInterface.runControlInterface(NUMBER_OF_UES,
numberOfThreads);
if (!success) {
System.err.println("Did not finish all creations.");
}
} catch (InterruptedException e) {
System.out.println("Oh no " + e.getMessage());
e.printStackTrace();
}
System.out
.println("Exiting −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−");
long result = controlInterface.getRunTime(TimeUnit.NANOSECONDS);
System.gc();
System.runFinalization();
Thread.sleep(50);
return result;
}
private long testNumberOfThreads(int numberOfThreads)
throws InterruptedException {
long[] results = new long[ROUNDS];
long avg = 0;
long smooth = 0;
double alpha = 0.125;
System.out.println("Testing: " + ROUNDS + " rounds, " + numberOfThreads
+ " threads, " + NUMBER_OF_UES + " UEs, " + WAIT_TIME
+ " wait time");
for (int i = 0; i < ROUNDS; i++) {
results[i] = testRoundsForThreads(numberOfThreads);
avg += results[i];
if (i == 0) {
smooth = results[i];
} else {
smooth = (long) ((1 − alpha) * smooth + alpha * results[i]);
}
}
avg /= ROUNDS;
for (int i = 0; i < ROUNDS; i++) {
System.out.println("Round " + (i + 1) + ": " + (results[i] / 1000)
+ " µs");
}
Arrays.sort(results);
long median = results[ROUNDS / 2];
System.out.println("Average: " + (avg / 1000) + " µs");
System.out.println("Smoothed: " + (smooth / 1000) + " µs");
System.out.println("Median: " + (median / 1000) + " µs");
return median;
}
}
package com.enea.simulator.tests;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.enea.simulator.ControlInterface;
public class PerformanceNoDelayTestMaxUE {
private static final int WARMUP_WAIT_TIME = 25;
private static final int WAIT_TIME = 1000;
private static final int ROUNDS = 20;
private static final int NR_THREADS = 4;
private static final int UE_INTERVAL = 1000;
private ControlInterface controlInterface;
@Before
public void setUp() throws Exception {
warmup();
}
private void warmup() {
System.out.println("Warming up...");
ControlInterface.test = true;
for (int i = 0; i < 100; i++) {
controlInterface = new ControlInterface(WARMUP_WAIT_TIME);
try {
controlInterface.runControlInterface(5, 3);
} catch (InterruptedException ignored) {
System.err.println("Warmup interrupted");
}
}
for (int i = 0; i < 50; i++) {
controlInterface = new ControlInterface(WARMUP_WAIT_TIME);
try {
controlInterface.runControlInterface(UE_INTERVAL, 5);
} catch (InterruptedException ignored) {
System.err.println("Warmup interrupted");
}
}
System.gc();
System.runFinalization();
System.out.println("Done warming up.");
}
@After
public void tearDown() throws Exception {
}
@Test
public void testRunControlInterface() {
ArrayList<Long> results = new ArrayList<Long>();
int numberOfUEs = UE_INTERVAL;
for (; results.size() == 0 || results.get(results.size() − 1) != −1; numberOfUEs += UE_INTERVAL) {
try {
results.add(testNumberOfUEs(numberOfUEs));
} catch (InterruptedException e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
System.out.println();
for (int i = 0; i < results.size() − 1; i++) {
System.out.println(((i + 1) * UE_INTERVAL) + "\t"
+ (results.get(i) / (1000 * 1000)));
}
System.out.println("Performance test done.");
}
private long testRoundsForUEs(int numberOfUEs) throws InterruptedException {
controlInterface = new ControlInterface(WAIT_TIME);
boolean success = false;
System.gc();
System.runFinalization();
Thread.sleep(50);
System.out
.println("Entering −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−");
success = controlInterface.runControlInterface(numberOfUEs, NR_THREADS);
if (!success) {
System.err.println("Did not finish all creations.");
}
System.out
.println("Exiting −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−");
long result = controlInterface.getRunTime(TimeUnit.NANOSECONDS);
System.gc();
System.runFinalization();
Thread.sleep(50);
if (!success) {
return −1;
}
return result;
}
private long testNumberOfUEs(int numberOfUEs) throws InterruptedException {
long[] results = new long[ROUNDS];
System.out.println("Testing: " + ROUNDS + " rounds, " + numberOfUEs
+ " UEs, wait time: " + WAIT_TIME + " wait time");
for (int i = 0; i < ROUNDS; i++) {
results[i] = testRoundsForUEs(numberOfUEs);
if (results[i] == −1) {
return −1;
}
}
for (int i = 0; i < ROUNDS; i++) {
System.out.println("Round " + (i + 1) + ": " + (results[i] / 1000)
+ " µs");
}
Arrays.sort(results);
long median = results[ROUNDS / 2];
System.out.println("Median: " + (median / 1000) + " µs");
return median;
}
}
package com.enea.simulator.signal.order;
import java.util.HashMap;
import com.enea.simulator.signal.SignalType;
public class DefaultSignalOrder implements SignalOrder {
private final HashMap<SignalType, SignalType> currentMappedToNext = new HashMap<SignalType, SignalType>();
public DefaultSignalOrder() {
currentMappedToNext.put(SignalType.CREATE_UE, SignalType.POWER_ON);
currentMappedToNext.put(SignalType.POWER_ON, SignalType.POWER_ON_ACK);
currentMappedToNext.put(SignalType.POWER_ON_ACK, SignalType.NEGOTIATE_CAPABILITIES);
currentMappedToNext.put(SignalType.NEGOTIATE_CAPABILITIES, SignalType.NEGOTIATE_CAPABILITIES_ACK);
currentMappedToNext.put(SignalType.NEGOTIATE_CAPABILITIES_ACK, SignalType.CREATE_UE_ACK);
currentMappedToNext.put(SignalType.CREATE_UE_ACK, SignalType.CALL_SETUP);
currentMappedToNext.put(SignalType.CALL_SETUP, SignalType.CALL_SETUP_ACK);
currentMappedToNext.put(SignalType.CALL_SETUP_ACK, SignalType.CALL_END);
currentMappedToNext.put(SignalType.CALL_END, SignalType.CALL_END_ACK);
currentMappedToNext.put(SignalType.CALL_END_ACK, SignalType.NONE);
}
@Override
public SignalType getNextSignalType(SignalType currentSignalType) {
return currentMappedToNext.get(currentSignalType);
}
}
package com.enea.simulator.signal.order;
import com.enea.simulator.signal.SignalType;
public interface SignalOrder {
public SignalType getNextSignalType(SignalType currentSignalType);
}
C. JAVA-IMPLEMENTATION
C.2
Litteraturförteckning
Utökad
Den utökade simulatorn är samma som den grundläggande med följande
ändringar.
SignalType:
CALL_SETUP, CALL_SETUP_ACK,
CALL_END, CALL_END_ACK,
DefaultSignalOrder - konstruktor:
currentMappedToNext.put(SignalType.NEGOTIATE_CAPABILITIES_ACK, SignalType.CREATE_UE_ACK);
bytt mot
currentMappedToNext.put(SignalType.NEGOTIATE_CAPABILITIES_ACK, SignalType.CALL_SETUP);
currentMappedToNext.put(SignalType.CALL_SETUP, SignalType.CALL_SETUP_ACK);
currentMappedToNext.put(SignalType.CALL_SETUP_ACK, SignalType.CALL_END);
currentMappedToNext.put(SignalType.CALL_END, SignalType.CALL_END_ACK);
currentMappedToNext.put(SignalType.CALL_END_ACK, SignalType.CREATE_UE_ACK);
SystemUnderTest - konstruktor:
responseSignalTypes.put(SignalType.CALL_SETUP, SignalType.CALL_SETUP_ACK);
responseSignalTypes.put(SignalType.CALL_END, SignalType.CALL_END_ACK);
SystemUnderTest:
private int runNumber = 0;
SystemUnderTest - handleSignal:
signalType = responseSignalTypes.get(signal.getSignalType());
bytt mot
if (runNumber % 100 == 1) {
signalType = SignalType.REJECT;
} else {
signalType = responseSignalTypes.get(signal.getSignalType());
}
C.3
Ytterligare utökad
Den ytterligare utökade simulatorn är samma som den utökade med följande
ändringar.
SignalType:
DATA_SETUP, DATA_SETUP_ACK,
DATA_END, DATA_END_ACK,
DefaultSignalOrder - konstruktor:
currentMappedToNext.put(SignalType.CALL_SETUP_ACK, SignalType.CREATE_UE_ACK);
bytt mot
currentMappedToNext.put(SignalType.CALL_SETUP_ACK, SignalType.CALL_END);
currentMappedToNext.put(SignalType.CALL_END, SignalType.CALL_END_ACK);
currentMappedToNext.put(SignalType.CALL_END_ACK, SignalType.DATA_SETUP);
currentMappedToNext.put(SignalType.DATA_SETUP, SignalType.DATA_SETUP_ACK);
currentMappedToNext.put(SignalType.DATA_SETUP_ACK, SignalType.DATA_END);
currentMappedToNext.put(SignalType.DATA_END, SignalType.DATA_END_ACK);
currentMappedToNext.put(SignalType.DATA_END_ACK, SignalType.CREATE_UE_ACK);
SystemUnderTest - konstruktor:
responseSignalTypes.put(SignalType.DATA_SETUP, SignalType.DATA_SETUP_ACK);
responseSignalTypes.put(SignalType.DATA_END, SignalType.DATA_END_ACK);
40
D. JETLANG-IMPLEMENTATION
D
D.1
Litteraturförteckning
Jetlang-implementation
Grundläggande
package com.enea.simulator;
import
import
import
import
java.util.concurrent.ExecutorService;
java.util.concurrent.Executors;
java.util.concurrent.TimeUnit;
java.util.logging.Logger;
import
import
import
import
import
import
import
org.jetlang.channels.MemoryRequestChannel;
org.jetlang.core.Callback;
org.jetlang.core.Disposable;
org.jetlang.core.DisposingExecutor;
org.jetlang.core.SynchronousDisposingExecutor;
org.jetlang.fibers.Fiber;
org.jetlang.fibers.PoolFiberFactory;
import
import
import
import
import
import
import
com.enea.simulator.behavior.Behavior;
com.enea.simulator.behavior.DefaultBehavior;
com.enea.simulator.behavior.TestBehavior;
com.enea.simulator.endpoint.SystemUnderTest;
com.enea.simulator.endpoint.UserEquipment;
com.enea.simulator.signal.Signal;
com.enea.simulator.signal.SignalType;
public class ControlInterface implements Callback<Signal> {
public static boolean test = true;
public static void main(String[] args) throws InterruptedException {
// Get number of UserEquipments to use from arguments
if (args.length < 1) {
System.err.println("Please specify number of UserEquipments");
return;
}
int nrOfUEs = Integer.parseInt(args[0]);
if (nrOfUEs < 1) {
System.err
.println("Please specify a positive number of UserEquipments");
return;
}
ControlInterface controlInterface = new ControlInterface(5000);
int numberOfThreads = 0;
// Get optional argument for number of threads
if (args.length > 1) {
int nrThreadsTemp = Integer.parseInt(args[1]);
if (nrThreadsTemp > 0) {
numberOfThreads = nrThreadsTemp;
41
}
}
// Start the control interface and catch unexpected exceptions.
try {
controlInterface.runControlInterface(nrOfUEs, numberOfThreads);
} catch (Exception e) {
System.err.println(e.getMessage());
e.printStackTrace();
}
}
// Non−static ////////////////////////////////////////////////////////////
// Used
private
private
private
for logging and benchmarking
final Logger log;
final long startTime;
long stopTime;
private final int waitTime;
private PoolFiberFactory fiberFactory;
private int createdUEs;
private int targetUEs;
private Disposable sutChannelDisposable;
public ControlInterface(int waitTime) {
startTime = System.nanoTime();
this.waitTime = waitTime;
log = Logger.getLogger(this.getClass().getName());
}
public void runControlInterface(int numberOfUEs)
throws InterruptedException {
runControlInterface(numberOfUEs, 0);
}
public boolean runControlInterface(final int numberOfUEs,
int numberOfThreads) throws InterruptedException {
targetUEs = numberOfUEs;
ExecutorService threadPool = createThreadPool(numberOfThreads);
fiberFactory = new PoolFiberFactory(threadPool);
SynchronousDisposingExecutor executor = new SynchronousDisposingExecutor();
final MemoryRequestChannel<Signal, Signal> sutChannel = initSUTAndGetChannel(executor);
Fiber createUeFiber = fiberFactory.create();
createUeFiber.start();
createUeFiber.execute(new Runnable() {
@Override
public void run() {
createUes(numberOfUEs, sutChannel);
}
});
Thread.sleep(waitTime);
log.info("Interrupting");
createUeFiber.dispose();
sutChannelDisposable.dispose();
executor.dispose();
threadPool.shutdown();
Thread.yield();
threadPool.shutdownNow();
boolean result = allCreated();
log.warning("" + getRunTime(TimeUnit.MILLISECONDS));
return result;
}
private MemoryRequestChannel<Signal, Signal> initSUTAndGetChannel(
DisposingExecutor executor) {
Fiber sutFiber = fiberFactory.create();
sutFiber.start();
MemoryRequestChannel<Signal, Signal> sutChannel = new MemoryRequestChannel<Signal, Signal>();
SystemUnderTest sut = new SystemUnderTest();
if (test) {
sut = new SystemUnderTest();
}
sutChannelDisposable = sutChannel.subscribe(executor, sut);
return sutChannel;
}
private boolean allCreated() {
boolean success;
synchronized (this) {
success = createdUEs == targetUEs;
}
if (success) {
log.warning("Got all CREATE_UE_ACK.");
} else {
log.warning("Did not get all CREATE_UE_ACK.");
}
return success;
}
private void createUes(int nrOfUEs,
MemoryRequestChannel<Signal, Signal> sutChannel) {
for (int i = 0; i < nrOfUEs; i++) {
Behavior behavior = new DefaultBehavior();
if (test) {
behavior = new TestBehavior();
}
Fiber ueFiber = fiberFactory.create();
ueFiber.start();
MemoryRequestChannel<Signal, Signal> ueChannel = new MemoryRequestChannel<Signal, Signal>();
UserEquipment ue = new UserEquipment(sutChannel, i, behavior,
new SynchronousDisposingExecutor());
ueChannel.subscribe(ueFiber, ue);
Signal signal = new Signal(SignalType.CREATE_UE);
ueChannel.publish(ueFiber, signal, this);
}
}
private ExecutorService createThreadPool(int numberOfThreads) {
int nrOfCPU = Runtime.getRuntime().availableProcessors();
int utilization = 1;
double wc = 2.0;
int nrOfThreads = (int) ((double) nrOfCPU * (double) utilization * (1.0 + wc));
// Override if user specified number of threads
if (numberOfThreads > 0) {
nrOfThreads = numberOfThreads;
}
log.warning("Creating " + nrOfThreads + " threads on " + nrOfCPU
+ " CPUs.");
ExecutorService threadPool = Executors.newFixedThreadPool(nrOfThreads);
return threadPool;
}
public long getRunTime(TimeUnit unit) {
return unit.convert(stopTime − startTime, TimeUnit.NANOSECONDS);
}
@Override
public void onMessage(final Signal message) {
if (message.getSignalType() != SignalType.CREATE_UE_ACK) {
log.severe("ControlInterface: Did not get CREATE_UE_ACK, got "
+ message.getSignalType());
throw new IllegalArgumentException(
"ControlInterface: Did not get CREATE_UE_ACK, got "
+ message.getSignalType());
}
log.fine("ControlInterface: Got CREATE_UE_ACK");
synchronized (this) {
createdUEs++;
stopTime = System.nanoTime();
}
}
}
package com.enea.simulator;
/**
* This class or method is thread safe and can be called without synchronization
* across threads.
*/
public @interface ThreadSafe {
}
package com.enea.simulator.behavior;
import com.enea.simulator.signal.SignalType;
public interface Behavior {
public void executeCalculations(SignalType signalType);
}
package com.enea.simulator.behavior;
import
import
import
import
java.io.BufferedWriter;
java.io.File;
java.io.FileWriter;
java.util.Random;
import com.enea.simulator.ThreadSafe;
import com.enea.simulator.signal.SignalType;
@ThreadSafe
public class DefaultBehavior implements Behavior{
private Random generator = new Random();
@Override
public void executeCalculations(SignalType signalType) {
if (signalType == SignalType.CALL_SETUP) {
writeAndDeleteFile(10000);
} else {
writeAndDeleteFile(1000);
}
}
private void writeAndDeleteFile(int rounds) {
try {
int number = generator.nextInt(100000);
FileWriter fstream = new FileWriter("tmpFile" + number + ".txt");
BufferedWriter out = new BufferedWriter(fstream);
for (int i = 0; i < rounds; i++) {
out.write("Dummy data123456");
}
out.close();
File file = new File("tmpFile" + number + ".txt");
file.delete();
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
}
}
}
package com.enea.simulator.behavior;
import com.enea.simulator.signal.SignalType;
public class NoDelayBehavior implements Behavior {
@Override
public void executeCalculations(SignalType signalType) {
}
}
package com.enea.simulator.behavior;
import com.enea.simulator.signal.SignalType;
public class TestBehavior implements Behavior {
@Override
public void executeCalculations(SignalType signalType) {
}
}
package com.enea.simulator.endpoint;
import java.util.HashMap;
import java.util.logging.Logger;
import org.jetlang.channels.Request;
import org.jetlang.core.Callback;
import com.enea.simulator.ThreadSafe;
import com.enea.simulator.signal.Signal;
import com.enea.simulator.signal.SignalType;
@ThreadSafe
public class SystemUnderTest implements Callback<Request<Signal, Signal>> {
private final HashMap<SignalType, SignalType> responseSignalTypes = new HashMap<SignalType, SignalType>();
private Logger log;
public SystemUnderTest() {
log = Logger.getLogger(this.getClass().getName());
initResponseSignals();
}
private void initResponseSignals() {
responseSignalTypes.put(SignalType.POWER_ON, SignalType.POWER_ON_ACK);
responseSignalTypes.put(SignalType.NEGOTIATE_CAPABILITIES,
SignalType.NEGOTIATE_CAPABILITIES_ACK);
responseSignalTypes.put(SignalType.CALL_SETUP,
SignalType.CALL_SETUP_ACK);
responseSignalTypes.put(SignalType.CALL_END, SignalType.CALL_END_ACK);
}
@Override
public void onMessage(final Request<Signal, Signal> request) {
final Signal requestSignal = request.getRequest();
log.fine("Processing signal: " + requestSignal.toString());
final SignalType signalType = responseSignalTypes.get(requestSignal
.getSignalType());
if (signalType == null) {
log.severe("Unknown signal " + requestSignal);
throw new IllegalArgumentException("Unknown signal "
+ requestSignal);
}
final Signal result = new Signal(signalType);
request.reply(result);
}
}
package com.enea.simulator.endpoint;
import java.util.HashMap;
import java.util.logging.Logger;
import
import
import
import
org.jetlang.channels.MemoryRequestChannel;
org.jetlang.channels.Request;
org.jetlang.core.Callback;
org.jetlang.core.DisposingExecutor;
import
import
import
import
import
com.enea.simulator.ThreadSafe;
com.enea.simulator.behavior.Behavior;
com.enea.simulator.behavior.TestBehavior;
com.enea.simulator.signal.Signal;
com.enea.simulator.signal.SignalType;
@ThreadSafe
public class UserEquipment implements Callback<Request<Signal, Signal>> {
private
private
private
private
private
private
final int id;
final Logger log;
final Behavior behavior;
final MemoryRequestChannel<Signal, Signal> sutChannel;
DisposingExecutor executor;
HashMap<SignalType, Callback<Signal>> callbacks = new HashMap<SignalType, Callback<Signal>>();
public UserEquipment(MemoryRequestChannel<Signal, Signal> sutChannel,
int id, Behavior behavior, DisposingExecutor executor) {
this.sutChannel = sutChannel;
this.id = id;
this.behavior = behavior;
this.executor = executor;
log = Logger.getLogger(this.getClass().getName());
initCallbacks();
}
private void initCallbacks() {
callbacks.put(SignalType.POWER_ON, new Callback<Signal>() {
@Override
public void onMessage(Signal message) {
if (message.getSignalType() != SignalType.POWER_ON_ACK) {
log.severe("Did not get POWER_ON_ACK, got "
+ message.getSignalType());
throw new IllegalArgumentException(
"Did not get POWER_ON_ACK, got "
+ message.getSignalType());
}
log.fine(createMessage("Got " + message.getSignalType()));
}
});
callbacks.put(SignalType.NEGOTIATE_CAPABILITIES,
new Callback<Signal>() {
@Override
public void onMessage(Signal message) {
if (message.getSignalType() != SignalType.NEGOTIATE_CAPABILITIES_ACK) {
log.severe("Did not get NEGOTIATE_CAPABILITIES_ACK, got "
+ message.getSignalType());
throw new IllegalArgumentException(
"Did not get NEGOTIATE_CAPABILITIES_ACK, got "
+ message.getSignalType());
}
log.fine(createMessage("Got " + message.getSignalType()));
}
});
callbacks.put(SignalType.CALL_SETUP, new Callback<Signal>() {
@Override
public void onMessage(Signal message) {
if (message.getSignalType() != SignalType.CALL_SETUP_ACK) {
log.severe("Did not get CALL_SETUP_ACK, got "
+ message.getSignalType());
throw new IllegalArgumentException(
"Did not get CALL_SETUP_ACK, got "
+ message.getSignalType());
}
log.fine(createMessage("Got " + message.getSignalType()));
}
});
callbacks.put(SignalType.CALL_END, new Callback<Signal>() {
@Override
public void onMessage(Signal message) {
if (message.getSignalType() != SignalType.CALL_END_ACK) {
log.severe("Did not get CALL_END_ACK, got "
+ message.getSignalType());
throw new IllegalArgumentException(
"Did not get CALL_END_ACK, got "
+ message.getSignalType());
}
log.fine(createMessage("Got " + message.getSignalType()));
}
});
}
public int getId() {
return id;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("UE id: ");
sb.append(id);
return sb.toString();
}
private void run(Request<Signal, Signal> controlInterfaceRequest) {
sendAndReceiveSignal(SignalType.POWER_ON);
sendAndReceiveSignal(SignalType.NEGOTIATE_CAPABILITIES);
log.info(createMessage("Sending " + SignalType.CREATE_UE_ACK));
controlInterfaceRequest.reply(new Signal(SignalType.CREATE_UE_ACK));
if (behavior.getClass() != TestBehavior.class) {
sendAndReceiveSignal(SignalType.CALL_SETUP);
sendAndReceiveSignal(SignalType.CALL_END);
}
}
private void sendAndReceiveSignal(SignalType signalToSend) {
log.fine(createMessage("Sending " + signalToSend));
sutChannel.publish(executor, new Signal(signalToSend),
callbacks.get(signalToSend));
behavior.executeCalculations(signalToSend);
}
private String createMessage(String message) {
StringBuilder sb = new StringBuilder();
sb.append("UE ");
sb.append(id);
sb.append(": ");
sb.append(message);
sb.append(".");
return sb.toString();
}
@Override
public void onMessage(Request<Signal, Signal> message) {
final Signal requestSignal = message.getRequest();
final SignalType signalType = requestSignal.getSignalType();
if (signalType != SignalType.CREATE_UE) {
log.severe("Did not get " + SignalType.CREATE_UE);
throw new IllegalArgumentException("Did not get "
+ SignalType.CREATE_UE);
}
run(message);
}
}
package com.enea.simulator.order;
import java.util.HashMap;
import com.enea.simulator.ThreadSafe;
import com.enea.simulator.signal.SignalType;
@ThreadSafe
public class DefaultSignalOrder implements SignalOrder {
private final HashMap<SignalType, SignalType> currentMappedToNext = new HashMap<SignalType, SignalType>();
public DefaultSignalOrder() {
currentMappedToNext.put(SignalType.CREATE_UE, SignalType.POWER_ON);
currentMappedToNext.put(SignalType.POWER_ON, SignalType.POWER_ON_ACK);
currentMappedToNext.put(SignalType.POWER_ON_ACK, SignalType.NEGOTIATE_CAPABILITIES);
currentMappedToNext.put(SignalType.NEGOTIATE_CAPABILITIES, SignalType.NEGOTIATE_CAPABILITIES_ACK);
currentMappedToNext.put(SignalType.NEGOTIATE_CAPABILITIES_ACK, SignalType.CREATE_UE_ACK);
currentMappedToNext.put(SignalType.CREATE_UE_ACK, SignalType.CALL_SETUP);
currentMappedToNext.put(SignalType.CALL_SETUP, SignalType.CALL_SETUP_ACK);
currentMappedToNext.put(SignalType.CALL_SETUP_ACK, SignalType.CALL_END);
currentMappedToNext.put(SignalType.CALL_END, SignalType.CALL_END_ACK);
currentMappedToNext.put(SignalType.CALL_END_ACK, SignalType.NONE);
}
@Override
public SignalType getNextSignalType(SignalType currentSignalType) {
return currentMappedToNext.get(currentSignalType);
}
}
package com.enea.simulator.order;
import com.enea.simulator.signal.SignalType;
public interface SignalOrder {
public SignalType getNextSignalType(SignalType currentSignalType);
}
package com.enea.simulator.signal;
import com.enea.simulator.ThreadSafe;
/**
* This class represents an immutable signal with a sender, a receiver, a signal
* type and optional delay.
*
* @author helm
*
*/
@ThreadSafe
public class Signal {
private final SignalType signal;
public Signal(final SignalType signal) {
this.signal = signal;
}
public SignalType getSignalType() {
return signal;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Signal: ");
sb.append(signal.toString());
return sb.toString();
}
}
package com.enea.simulator.signal;
import com.enea.simulator.ThreadSafe;
@ThreadSafe
public enum SignalType {
POWER_ON, POWER_ON_ACK,
NEGOTIATE_CAPABILITIES, NEGOTIATE_CAPABILITIES_ACK,
CREATE_UE, CREATE_UE_ACK,
CALL_SETUP, CALL_SETUP_ACK,
CALL_END, CALL_END_ACK,
NONE;
public static SignalType first() {
return CREATE_UE;
}
public static SignalType last() {
return NONE;
}
public static SignalType setupDone() {
return CREATE_UE_ACK;
}
}
package com.enea.simulator.tests;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.enea.simulator.ControlInterface;
public class PerformanceNoDelayTest {
private static final int WARMUP_WAIT_TIME = 25;
private static final int WAIT_TIME = 5000;
private static final int ROUNDS = 20;
private static final int NUMBER_OF_UES = 100000;
private static final int MAX_THREADS = 10;
private ControlInterface controlInterface;
@Before
public void setUp() throws Exception {
warmup();
}
private void warmup() {
System.out.println("Warming up...");
ControlInterface.test = true;
for (int i = 0; i < 50; i++) {
controlInterface = new ControlInterface(WARMUP_WAIT_TIME);
try {
controlInterface.runControlInterface(5, 3);
} catch (InterruptedException ignored) {
System.err.println("Warmup interrupted");
}
}
for (int i = 0; i < 5; i++) {
controlInterface = new ControlInterface(WAIT_TIME);
try {
controlInterface.runControlInterface(NUMBER_OF_UES, MAX_THREADS);
} catch (InterruptedException ignored) {
System.err.println("Warmup interrupted");
}
}
System.gc();
System.runFinalization();
System.out.println("Done warming up.");
}
@After
public void tearDown() throws Exception {
}
@Test
public void testRunControlInterface() {
long[] results = new long[MAX_THREADS];
for (int i = 1; i < MAX_THREADS; i++) {
try {
results[i] = testNumberOfThreads(i + 1);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
for (int i = 1; i < MAX_THREADS; i++) {
System.out.println((i + 1) + " threads: " + (results[i] / 1000)
+ " µs");
}
System.out.println("Performance test done.");
}
private long testRoundsForThreads(int numberOfThreads)
throws InterruptedException {
controlInterface = new ControlInterface(WAIT_TIME);
System.gc();
System.runFinalization();
Thread.sleep(50);
System.out
.println("Entering −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−");
try {
boolean success = controlInterface.runControlInterface(NUMBER_OF_UES,
numberOfThreads);
if (!success) {
System.err.println("Did not finish all creations.");
}
} catch (InterruptedException e) {
System.out.println("Oh no " + e.getMessage());
e.printStackTrace();
}
System.out
.println("Exiting −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−");
long result = controlInterface.getRunTime(TimeUnit.NANOSECONDS);
System.gc();
System.runFinalization();
Thread.sleep(50);
return result;
}
private long testNumberOfThreads(int numberOfThreads)
throws InterruptedException {
long[] results = new long[ROUNDS];
long avg = 0;
long smooth = 0;
double alpha = 0.125;
System.out.println("Testing: " + ROUNDS + " rounds, " + numberOfThreads
+ " threads, " + NUMBER_OF_UES + " UEs, " + WAIT_TIME
+ " wait time");
for (int i = 0; i < ROUNDS; i++) {
results[i] = testRoundsForThreads(numberOfThreads);
avg += results[i];
if (i == 0) {
smooth = results[i];
} else {
smooth = (long) ((1 − alpha) * smooth + alpha * results[i]);
}
}
avg /= ROUNDS;
for (int i = 0; i < ROUNDS; i++) {
System.out.println("Round " + (i + 1) + ": " + (results[i] / 1000)
+ " µs");
}
Arrays.sort(results);
long median = results[ROUNDS / 2];
System.out.println("Average: " + (avg / 1000) + " µs");
System.out.println("Smoothed: " + (smooth / 1000) + " µs");
System.out.println("Median: " + (median / 1000) + " µs");
return median;
}
}
package com.enea.simulator.tests;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.enea.simulator.ControlInterface;
public class PerformanceNoDelayTestMaxUE {
private static final int WARMUP_WAIT_TIME = 25;
private static final int WAIT_TIME = 1000;
private static final int ROUNDS = 20;
private static final int NR_THREADS = 4;
private static final int UE_INTERVAL = 1000;
private ControlInterface controlInterface;
@Before
public void setUp() throws Exception {
warmup();
}
private void warmup() {
System.out.println("Warming up...");
ControlInterface.test = true;
for (int i = 0; i < 50; i++) {
controlInterface = new ControlInterface(WARMUP_WAIT_TIME);
try {
controlInterface.runControlInterface(5, 3);
} catch (InterruptedException ignored) {
System.err.println("Warmup interrupted");
}
}
for (int i = 0; i < 5; i++) {
controlInterface = new ControlInterface(WAIT_TIME);
try {
controlInterface.runControlInterface(UE_INTERVAL, NR_THREADS);
} catch (InterruptedException ignored) {
System.err.println("Warmup interrupted");
}
}
System.gc();
System.runFinalization();
System.out.println("Done warming up.");
}
@After
public void tearDown() throws Exception {
}
@Test
public void testRunControlInterface() {
ArrayList<Long> results = new ArrayList<Long>();
int numberOfUEs = UE_INTERVAL;
for (; results.size() == 0 || results.get(results.size() − 1) != −1; numberOfUEs += UE_INTERVAL) {
try {
results.add(testNumberOfUEs(numberOfUEs));
} catch (InterruptedException e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
for (int i = 0; i < results.size() − 1; i++) {
System.out.println(((i + 1) * UE_INTERVAL) + " "
+ (results.get(i) / (1000 * 1000)));
}
System.out.println();
for (int i = 0; i < results.size() − 1; i++) {
System.out.println(((i + 1) * UE_INTERVAL) + " UEs: "
+ (results.get(i) / (1000 * 1000)) + " ms");
}
System.out.println("Performance test done.");
}
private long testRoundsForUEs(int numberOfUEs) throws InterruptedException {
controlInterface = new ControlInterface(WAIT_TIME);
boolean success = false;
System.gc();
System.runFinalization();
System.out
.println("Entering −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−");
success = controlInterface.runControlInterface(numberOfUEs, NR_THREADS);
if (!success) {
System.err.println("Did not finish all creations.");
}
System.out
.println("Exiting −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−");
long result = controlInterface.getRunTime(TimeUnit.NANOSECONDS);
System.gc();
System.runFinalization();
if (!success) {
return −1;
}
return result;
}
private long testNumberOfUEs(int numberOfUEs) throws InterruptedException {
long[] results = new long[ROUNDS];
System.out.println("Testing: " + ROUNDS + " rounds, " + numberOfUEs
+ " UEs, wait time: " + WAIT_TIME + " wait time");
for (int i = 0; i < ROUNDS; i++) {
results[i] = testRoundsForUEs(numberOfUEs);
if (results[i] == −1) {
return −1;
}
}
for (int i = 0; i < ROUNDS; i++) {
System.out.println("Round " + (i + 1) + ": " + (results[i] / 1000)
+ " µs");
}
Arrays.sort(results);
long median = results[ROUNDS / 2];
System.out.println("Median: " + (median / 1000) + " µs");
return median;
}
}
D. JETLANG-IMPLEMENTATION
D.2
Litteraturförteckning
Utökad
Den utökade simulatorn är samma som den grundläggande med följande
ändringar.
SignalType:
CALL_SETUP, CALL_SETUP_ACK,
CALL_END, CALL_END_ACK,
SystemUnderTest - konstruktor:
responseSignalTypes.put(SignalType.CALL_SETUP, SignalType.CALL_SETUP_ACK);
responseSignalTypes.put(SignalType.CALL_END, SignalType.CALL_END_ACK);
SystemUnderTest:
private int runNumber = 0;
SystemUnderTest - handleSignal:
signalType = responseSignalTypes.get(signal.getSignalType());
bytt mot
if (runNumber % 100 == 1) {
signalType = SignalType.REJECT;
} else {
signalType = responseSignalTypes.get(signal.getSignalType());
}
UserEquipment - initCallbacks:
callbacks.put(SignalType.CALL_SETUP, new Callback<Signal>() {
@Override
public void onMessage(Signal message) {
if (message.getSignalType() == SignalType.REJECT) {
log.fine("UE got REJECT, trying again");
sendAndReceiveSignal(SignalType.CALL_SETUP);
}
else if (message.getSignalType() != SignalType.CALL_SETUP_ACK) {
log.severe("Did not get CALL_SETUP_ACK, got " + message.getSignalType());
throw new IllegalArgumentException("Did not get CALL_SETUP_ACK, got "
+ message.getSignalType());
}
log.fine(createMessage("Got " + message.getSignalType()));
}
});
callbacks.put(SignalType.CALL_END, new Callback<Signal>() {
@Override
public void onMessage(Signal message) {
if (message.getSignalType() == SignalType.REJECT) {
log.fine("UE got REJECT, trying again");
sendAndReceiveSignal(SignalType.CALL_END);
}
else if (message.getSignalType() != SignalType.CALL_END_ACK) {
log.severe("Did not get CALL_END_ACK, got " + message.getSignalType());
throw new IllegalArgumentException("Did not get CALL_END_ACK, got "
+ message.getSignalType());
}
log.fine(createMessage("Got " + message.getSignalType()));
}
});
UserEquipment - run:
sendAndReceiveSignal(SignalType.CALL_SETUP);
sendAndReceiveSignal(SignalType.CALL_END);
55
D. JETLANG-IMPLEMENTATION
D.3
Litteraturförteckning
Ytterligare utökad
Den ytterligare utökade simulatorn är samma som den utökade med följande
ändringar.
SignalType:
DATA_SETUP, DATA_SETUP_ACK,
DATA_END, DATA_END_ACK,
SystemUnderTest - konstruktor:
responseSignalTypes.put(SignalType.DATA_SETUP, SignalType.DATA_SETUP_ACK);
responseSignalTypes.put(SignalType.DATA_END, SignalType.DATA_END_ACK);
UserEquipment - initCallbacks:
callbacks.put(SignalType.DATA_SETUP, new Callback<Signal>() {
@Override
public void onMessage(Signal message) {
if (message.getSignalType() == SignalType.REJECT) {
log.fine("UE got REJECT, trying again");
sendAndReceiveSignal(SignalType.DATA_SETUP);
}
else if (message.getSignalType() != SignalType.DATA_SETUP_ACK) {
log.severe("Did not get DATA_SETUP_ACK, got " + message.getSignalType());
throw new IllegalArgumentException("Did not get DATA_SETUP_ACK, got "
+ message.getSignalType());
}
log.fine(createMessage("Got " + message.getSignalType()));
}
});
callbacks.put(SignalType.DATA_END, new Callback<Signal>() {
@Override
public void onMessage(Signal message) {
if (message.getSignalType() == SignalType.REJECT) {
log.fine("UE got REJECT, trying again");
sendAndReceiveSignal(SignalType.CALL_END);
}
else if (message.getSignalType() != SignalType.DATA_END_ACK) {
log.severe("Did not get DATA_END_ACK, got " + message.getSignalType());
throw new IllegalArgumentException("Did not get DATA_END_ACK, got "
+ message.getSignalType());
}
log.fine(createMessage("Got " + message.getSignalType()));
}
});
UserEquipment - run:
sendAndReceiveSignal(SignalType.DATA_SETUP);
sendAndReceiveSignal(SignalType.DATA_END);
56
E. ERLANG-IMPLEMENTATION
E
E.1
Litteraturförteckning
Erlang-implementation
Grundläggande
−module(behavior).
−export([default/1, test/1]).
default(Signal) −>
%io:fwrite("In behavior\n"),
ok.
test(Signal) −>
ok.
57
−module(max_ue).
−export([start/0]).
start() −>
warmup(100),
test_number_of_ues(1000).
warmup(0) −>
true;
warmup(N) −>
control_interface:run(1000),
warmup(N − 1).
test_number_of_ues(NumberOfUes) −>
Result = test_rounds(NumberOfUes, 20, []),
if Result =/= false −>
if Result =< 1000000 −>
io:fwrite(integer_to_list(trunc(NumberOfUes))),
io:fwrite("\t"),
io:fwrite(integer_to_list(trunc(Result / 1000))),
io:fwrite("\n"),
test_number_of_ues(NumberOfUes + 1000);
true −>
true
end;
true −>
true
end.
test_rounds(_NumberOfUes, 0, Results) −>
median(Results);
test_rounds(NumberOfUes, NumberOfRounds, Results) −>
{ExecutionTimeInMicros, Result} = timer:tc(control_interface, run, [NumberOfUes]),
if Result == NumberOfUes −>
test_rounds(NumberOfUes, NumberOfRounds − 1, [ExecutionTimeInMicros|Results]);
true −>
false
end.
median(List) when is_list(List) −>
SList = lists:sort(List),
Length = length(SList),
case even_or_odd(Length) of
even −> [A,B] = lists:sublist(SList, round(Length/2), 2), (A+B)/2;
odd −> lists:nth( round((Length+1)/2), SList )
end.
even_or_odd(Num) when is_integer(Num) −>
if
Num band 1 == 0 −> even;
true
−> odd
end.
−module(system_under_test).
−export([start/0]).
start() −>
loop(1).
loop(RunNumber) −>
ControlInterface = whereis(controlInterface),
receive
{ControlInterface, cancel} −>
%io:fwrite("SystemUnderTest exiting\n"),
true;
{Ue, power_on} −>
Ue ! {self(), power_on_ack},
loop(RunNumber + 1);
{Ue, negotiate_capabilities} −>
Ue ! {self(), negotiate_capabilities_ack},
loop(RunNumber + 1);
end.
−module(user_equipment).
−export([start/1]).
start(Behavior) −>
loop(Behavior).
loop(Behavior) −>
receive
{ControlInterface, create_ue} −>
%io:fwrite("UE got create_ue\n"),
Sut = whereis(sut),
apply(behavior, Behavior, [power_on]),
send_signal_and_get_ack(Sut, power_on, power_on_ack),
%io:fwrite("UE got power_on_ack\n"),
send_signal_and_get_ack(Sut, negotiate_capabilities, negotiate_capabilities_ack),
%io:fwrite("UE got negotiate_capabilities_ack\n"),
apply(behavior, Behavior, [call_setup]),
apply(behavior, Behavior, [create_ue_ack]),
ControlInterface ! {self(), create_ue_ack}
end.
send_signal_and_get_ack(Sut, Signal, Ack) −>
Sut ! {self(), Signal},
receive
{Sut, Ack} −>
true;
end.
E. ERLANG-IMPLEMENTATION
E.2
Litteraturförteckning
Utökad
Den utökade simulatorn är samma som den grundläggande med följande
ändringar.
system_under_test - start:
loop().
bytt mot
loop(1).
system_under_test - loop:
borttaget
{Ue, power_on} ->
Ue ! {self(), power_on_ack},
loop(RunNumber + 1);
{Ue, negotiate_capabilities} ->
Ue ! {self(), negotiate_capabilities_ack},
loop(RunNumber + 1);
{Ue, power_on} ->
if RunNumber rem 100 == 1 ->
Ue ! {self(), reject};
true ->
Ue ! {self(), power_on_ack}
end,
loop(RunNumber + 1);
{Ue, negotiate_capabilities} ->
if RunNumber rem 100 == 1 ->
Ue ! {self(), reject};
true ->
Ue ! {self(), negotiate_capabilities_ack}
end,
loop(RunNumber + 1);
{Ue, call_setup} ->
if RunNumber rem 100 == 1 ->
Ue ! {self(), reject};
true ->
Ue ! {self(), call_setup_ack}
end,
loop(RunNumber + 1);
{Ue, call_end} ->
if RunNumber rem 100 == 1 ->
Ue ! {self(), reject};
true ->
Ue ! {self(), call_end_ack}
end,
loop(RunNumber + 1)
user_equipment - loop:
apply(behavior, Behavior, [call_setup]),
send_signal_and_get_ack(Sut, call_setup, call_setup_ack),
%io:fwrite("UE got call_setup_ack\n"),
apply(behavior, Behavior, [call_end]),
send_signal_and_get_ack(Sut, call_end, call_end_ack),
%io:fwrite("UE got call_end_ack\n"),
E.3
Ytterligare utökad
Den ytterligare utökade simulatorn är samma som den utökade med följande
ändringar.
system_under_test - loop:
60
E. ERLANG-IMPLEMENTATION
Litteraturförteckning
{Ue, data_setup} ->
if RunNumber rem 100 == 1 ->
Ue ! {self(), reject};
true ->
Ue ! {self(), data_setup_ack}
end,
loop(RunNumber + 1);
{Ue, data_end} ->
if RunNumber rem 100 == 1 ->
Ue ! {self(), reject};
true ->
Ue ! {self(), data_end_ack}
end,
loop(RunNumber + 1)
user_equipment - loop:
send_signal_and_get_ack(Sut, data_setup, data_setup_ack),
%io:fwrite("UE got data_setup_ack\n"),
send_signal_and_get_ack(Sut, data_end, data_end_ack),
%io:fwrite("UE got data_end_ack\n"),
61
På svenska
Detta dokument hålls tillgängligt på Internet – eller dess framtida ersättare –
under en längre tid från publiceringsdatum under förutsättning att inga extraordinära omständigheter uppstår.
Tillgång till dokumentet innebär tillstånd för var och en att läsa, ladda ner,
skriva ut enstaka kopior för enskilt bruk och att använda det oförändrat för
ickekommersiell forskning och för undervisning. Överföring av upphovsrätten
vid en senare tidpunkt kan inte upphäva detta tillstånd. All annan användning av
dokumentet kräver upphovsmannens medgivande. För att garantera äktheten,
säkerheten och tillgängligheten finns det lösningar av teknisk och administrativ
art.
Upphovsmannens ideella rätt innefattar rätt att bli nämnd som upphovsman i
den omfattning som god sed kräver vid användning av dokumentet på ovan
beskrivna sätt samt skydd mot att dokumentet ändras eller presenteras i sådan
form eller i sådant sammanhang som är kränkande för upphovsmannens litterära
eller konstnärliga anseende eller egenart.
För ytterligare information om Linköping University Electronic Press se
förlagets hemsida http://www.ep.liu.se/
In English
The publishers will keep this document online on the Internet - or its possible
replacement - for a considerable time from the date of publication barring
exceptional circumstances.
The online availability of the document implies a permanent permission for
anyone to read, to download, to print out single copies for your own use and to
use it unchanged for any non-commercial research and educational purpose.
Subsequent transfers of copyright cannot revoke this permission. All other uses
of the document are conditional on the consent of the copyright owner. The
publisher has taken technical and administrative measures to assure authenticity,
security and accessibility.
According to intellectual property law the author has the right to be
mentioned when his/her work is accessed as described above and to be protected
against infringement.
For additional information about the Linköping University Electronic Press
and its procedures for publication and for assurance of document integrity,
please refer to its WWW home page: http://www.ep.liu.se/
© Henrik Holmberg