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