• No results found

I detta kapitel redogörs progression under genomförandet. Implementationens version hänvisar till github-projektets hash ID från en commit.

29

5.3.1 OSM-data

Från början var tanken att automatisera exportering av OSM-data genom att ladda ner hela ”Planet.osm” filen från OSM-projektets webbplattform och sedan filtrera och extrahera delar av kartan med hjälp av program. Detta visade sig vara svårt då programmen och språken för att filtrera ”.osm” filer hade en inlärningskurva. Manuell nerladdning av områden från Overpass-API visade sig var mer tidseffektivt och enklare än att lära sig programmen och att automatisera exportering av OSM-data.

Från början var det tänkt att ”.osm” filer skulle parsas direkt till struct:s som genererats av schemakompilatorerna. Detta var komplicerat att implementera då den binära buffern som byggs i FlatBuffers måste byggas bottom-up och Go:s inbyggda XML-parser parsar filer

top-down. I en senare version #327547b används ett annat tillvägagångssätt. När en ”.osm” fil blir

inläst på serversidan parsas datan till en egendefinierad struct som inte blivit genererad av schemakompilatorerna. Vi kallar instansen av denna struct X. Objektet X används som ett mellansteg vid strukturering av OSM-data till PB- och FBS-format. När X är skapad loopas alla X:s fält igenom och tilldelas till motsvarande fält i en instans av en struct genererad av schemakompilatorerna, beroende på vilket serialiseringsformat som används. Denna loopning är också struktureringsoperationen i flödet. Koden för den egendefinierade struct:en och parsning av OSM-data till denna inspirerades till stor del av glaslos (2017).

5.3.2 Mätningar

I en tidig version #abc3a35 mättes deserialiseringstid för FBS på klienten som tiden det tar lagra den binära datan från serversvaret i struct:en genererad av schemakompilatorn. Då det inte sker någon riktig deserialisering resulterade detta tillvägagångssätt i att deserialiseringstid för FBS alltid blev 0 ms. Detta beror på att FBS inte kräver något mellansteg vid parsning och uppackning för att kunna nå data (Giaimo et al., 2015), den binära datan lagras i ett fält i en struct. Schemakompilatorn för FBS förser en med getter metoder som ger åtkomst till fälten i schemafilen och det är här deserialiseringen sker. Då vi vill åt all kartdata för att kunna rendera en karta mäts deserialiseringstid i en senare version #46d9651 som tiden det tar att lagra datan i struct:en genererad av schemakompilatorn samt kalla på samtliga getter metoder.

Som nämnts tidigare innebär serialisering i FBS att bygga en binär buffer. Således struktureras datan samtidigt som serialisering sker. I PB innebär serialisering att göra om ett redan strukturerat objekt till en bytesekvens. I en tidig version #754a50d var detta något jag inte tog hänsyn till. Konsekvensen var att strukturering- och serialiseringstid mättes för FBS men inte för PB. I en senare version #327547b mäts struktureringstid och serialiseringstid för både PB och FBS. Då struktureringstid och serialiseringstid i FBS sker samtidigt anges struktureringstid i FBS alltid som 0 ms.

5.3.3 Designval i testmiljöer

I en tidig version #4f49c75 implementerades WebSocket implementationen med go-routines (användartrådar) för skriv- och läsoperationer och channels för att synkronisera dessa användartrådar. Denna implementationen inspirerades av en chatt-applikation beskriven i boken Go Programming Blueprints (Ryer, 2016) där fler klienter kan ansluta sig till ett chatt-rum och skicka meddelanden samtidigt. Allteftersom min kunskap om WebSockets i Go förbättrades under genomförandet förstod jag att dessa go-routines och channels inte var nödvändiga. Detta då endast en klient ansluter sig till servern i testmiljön. I en senare version #327547b har denna onödiga komplexitet eliminerats och flödet sker istället sekventiellt.

30

I WebSocket sänds endast HTTP headers under förbindelsen. Detta skapade komplikationer vad gäller hur mätdata skulle överföras server till klient i WebSocket implementationen. Ett första tillvägagångssätt var att inkludera mätdatan på servern i schemafilerna för respektive serialiseringsformat. Detta tillvägagångssätt avbröts under implementationen då vi inte vet serialiseringstid förens efter att serialiseringsoperationen skett och binär data är svår att mutera. I en senare version #327547b typ-omvandlas istället samtlig mätdata till dess bytesekvens representation och läggs till i slutet på den serialiserade datan. På klienten extraheras denna data innan deserialisering av kartdatan sker. Tillvägagångssättet utgår från att samtliga noder i kommunikationen processar binär data med den minst signifikanta bit:en först (little endian).

func handler(w http.ResponseWriter, r *http.Request) { ...

data = appendFloat64ToBytes(data, serializationTime) ...

}

func float64ToBytes(f float64) []byte { bits := math.Float64bits(f)

bytes := make([]byte, 8)

binary.LittleEndian.PutUint64(bytes, bits) return bytes

}

func appendFloat64ToBytes(data []byte, f float64) []byte { buf := float64ToBytes(f)

for i := 0; i < len(buf); i++ { data = append(data, buf[i]) }

return data }

Figur 13 Mätdata i serversvaret i WebSocket Implementationen

Figur 13 illustrerar hur serialiseringstid läggs till i den serialiserade bytesekvensen på servern i WebSocket implementationen. Serialiseringstid är av datatypen float64 som tar upp 8 bytes i minnet. Funktionen float64ToBytes tar emot en float64 och returnerar dess bytesekvens representation. Funktionen appendFloat64ToBytes lägger till bytesekvens representationen av en float64 till en redan existerande bytesekvens, dvs. data. I funktionen handler som hanterar logiken för WebSocket kommunikationen i serverprogrammet, läggs serialiseringstiden till i den serialiserade datan (data).

Från början var tanken att logga mätdata på två olika ställen, både på klienten och på servern. Med risk av att loggningarna sker osynkroniserat sänds istället i en senare version #327547b all mätdata från servern med till klienten i serversvaret. Utöver all mätdata sänder servern dessutom tillbaka det ID som klienten sände med i förfrågningen. På klienten valideras detta ID. Om ID från klienten inte matchar ID från servern har ett fel inträffat och mätningarna avbryts.

31

...

for i := 0; i < *iterations; i++ { ...

// Request data from server:

requestMessage := struct {

ID uint32 `json:"id"`

SerializationFormat string `json:"serializationFormat"` }{ID: uint32(i + 1), SerializationFormat: *serializationFormat} if err := conn.WriteJSON(&requestMessage); err != nil {

log.Println(err) break

} ...

// Read response data from server:

_, data, err := conn.ReadMessage() if err != nil {

log.Println(err) break

} ...

// Extract and validate id from data:

m.ID = extractUint32FromBytes(data, len(data)-4, len(data)) if m.ID != requestMessage.ID {

log.Println("ID from requestMessage doesn't match ID recieved from server") break } ... } ...

Figur 14 Validering av ID i WebSocket implementationen

Figur 14 illustrerar validering av ID på klienten. Klienten börjar med att skriva ett objekt med information om ID och serialiseringsformat i JSON-format till servern (conn.WriteJSON). När servern har processat förfrågan från klienten sänder den tillbaka ett svar med data (kartdata och mätdata) som klienten läser in (conn.ReadMessage). Klienten extrahera ID från datan (m.ID) och validerar ID emot det ID den själv sände med i sin förfrågan (requestMessage.ID). Om ID från servern visar sig vara invalid avbryter klienten pågående test och skriver ett meddelande till konsolen.

Related documents