Basic personligt

Daniel Brahneborgs blogg

Spara logfiler med “cp -al”

SMS-systemet som jag jobbar med kan producera ganska mycket data i logfilerna, när det går mycket trafik. När de har nått en viss storlek stängs de, byts namn på, och så börjar man på nästa fil. Namnbyten använder samma metod som logrotate, dvs filen X döps om till X.1, X.1 blir X.2, X.2 blir X.3, osv. Den “sista” filen (X.10, eller hur många versioner man nu har valt att behålla) tas bort. På det sättet har man full kontroll över hur mycket data som kommer behövas. På den server som jag ska undersöka idag är det ungefär 20 GB totalt.

När någonting går fel, vill man titta i de där filerna, för att se vad som har hänt. Om servern kan stoppas är det inga problem, men ofta går inte det. Istället sitter den och fyller på mer data, byter namn på filer, och några minuter senare kan datat man ville åt vara borta. Vissa filer roteras ut snabbare än andra, så livslängden för datat kan variera väldigt mycket.

Den enkla lösningen är ju då att göra en kopia på datat med en gång, så man får lugn och ro. Jaha, på 20 GB? Det går inte direkt blixtsnabbt, så mycket data hinner försvinna långt innan den kopieringen är klar. Dessutom kopierar man förmodligen en massa filer i onödan. Datat i vissa filer kanske tillkommer så pass långsamt att de ändå inte skulle hinna försvinna, eller så innehåller de data som inte är intressant för den situation man håller på att undersöka.

Kommandot “cp -al” to the rescue. I mitt fall “cp -al log log.saved”. Istället för att kopiera varje fil i katalogen “log”, gör den en hård länk. Fildatat existerar fortfarande bara i ett exemplar, men har nu två katalogposter. Det är först när båda två tas bort, som själva datat försvinner. Att skapa några dussin katalogentryn går på nolltid, så man tappar ingenting. När de äldsta filerna tas bort ur “log”-katalogen, finns fortfarande ett entry i “log.saved”. Vartefter tiden går, så blir det fler och fler filer som ligger ensamna i “log.saved”. De långsamma filerna ligger kvar med dubbla entryn, och tar därför ingen extra plats. Nytt data tillkommer visserligen även i de filer som inte hinner försvinna, men med tidsstämplar i filerna så gör det ingenting. I vilket fall som helst finns datat i de filerna fortfarande bara i ett exemplar.

Om man har väldigt ont om plats eller behöver ha kvar logfilerna en längre tid, kan man sedan kopiera “log.saved”-katalogen till en annan disk efteråt. Effekten blir ungefär densamma, och huvudsaken är att man inte tappar något data under tiden som kopieringen pågår.

När man är klar, är det bara att ta bort hela “log.saved”-katalogen. De filer som är unika där, försvinner helt. De som även finns i “log”-katalogen, blir nu ensamna där. När de så småningom tas bort även därifrån, försvinner även deras data, precis som vanligt.

Unix är bra grejer. Förutom Mac OS X, där “-l” tydligen inte finns, trots att hårda länkar finns i filsystemet.

February 25th, 2013 Posted by Daniel Brahneborg | blogg | 5 comments

Lärdomar av “const”

Jag har ägnat lite tid åt att lägga till “const” i ett någorlunda stort C-program. Jag visste att det skulle göra nytta både för prestanda och läsbarhet, men det visade sig ge fler effekter än så.

Första steget var att leta upp och ändra på de funktioner där en sträng tas emot enbart för läsning. Till exempel strcpy(), som kopierar en sträng från ett ställe till ett annat. Första parametern där är “destination”, dvs stället där resultatet ska hamna. Den andra är “source”, dvs strängen som ska kopieras. Den senare läser man bara, så den kan vara “const char*” istället för bara “char*” (som den förra parametern är). I och med detta så hjälper man kompilatorn att lägga ut lite mer effektiv kod. Kanske ännu viktigare är att man också talar om för andra programmerare som tittar på samma kod att det datat inte kommer ändras på. Man ska inte underskatta nyttan med att göra koden läsbar för andra.

Det här sprider sig sedan uppåt i koden. Om funktionen A anropar B och C, och ingen av dem ändrar på datat i en viss sträng, kan den markeras som “const” även för A.

I och med att man har ett väldigt snävt fokus när man går igenom koden, är det lätt att sakta men säkert gå igenom fil efter fil. Man har bara en enda sak att hålla utkik efter, vilket gör att man slipper bli snurrig. Därmed blir det väldigt lätt att upptäcka att en viss parameter kanske inte används alls och därmed kan tas bort, för att få enklare och renare kod. Eller att någon viss funktion inte används alls, för den delen.

I något fall kopierade jag strängen till en ny buffert utifall att den skulle ändras på av en av de anropade funktionerna. Alla visade sig få const-markeringar, så kopieringen var onödig. Kunskap är makt, och i det här fallet leder det även till snabbare och säkrare kod.

Funktioner som inte ändrar på datat de får in, är dessutom enormt mycket enklare att testa, eftersom det både är enklare att kontrollera datat som de jobbar med, och för att de tenderar att vara enklare och anropa färre andra funktioner.

Ganska snart utkristalliserade det sig tre grupper av funktioner.

  1. Rena test-funktioner, där alla parametrar var const. De gjorde jämförelser, och kollade status på det ena eller andra.
  2. Rena modifieringsfunktioner. Ingen av parametrarna var const, utan allting pillades på. De hanterade saker som att skapa en uppkoppling, inloggningar, inlägg och borttagningar i köer, och så vidare.
  3. De lite mer schizofrena, som gjorde både och.

Enligt principen att allting intressant händer i övergångar, är det även nu den sista gruppen som är mest spännande. Framför allt för de strängar som den skapade själv. Synnerligen ofta hade de nämligen precis samma tre delar.

  1. Allokera minne för strängen, och lägg dit data.
  2. Gör någonting, där den här strängen skickas med som parameter till en eller flera andra funktioner. Den här delen utgjorde i princip hela funktionen.
  3. Lämna tillbaka minnet.

I nästan samtliga fall så hanterades strängen i steg 2 som om den vore const. Den delen kan då refaktoreras ut till en ny funktion, för att behålla renheten mellan grupp 1 och 2 ovan. Dessutom blir det enklare att testa den delen, eftersom man blir friare i att populera strängen som man vill. Dessutom minskar storleken på originalfunktionen, vilket gör det lättare att säkerställa att man inte glömmer bort något allokerat minne eller något sådant.

Nästa steg i “const”-projektet var att göra samma sak för övriga datatyper, vilket i realiteten innebar structs av olika slag. Då blev den här formen “allokera resurser och initiera data – undersök data – lämna tillbaka resurser” ännu tydligare. Även här hittades en del fält som aldrig användes, och kod som aldrig kördes av en eller annan anledning.

Att bara göra ren “code review” tror jag inte på. Det finns för många saker att undersöka, och är programmet längre än 10 rader långt blir det för komplext. Genom att välja ett väldigt smalt fokus på det här sättet, blir det mycket enklare. Man kanske inte hittar alla buggar, men å andra sidan har man nu en sportslig chans att hitta någon överhuvudtaget. Ett är mer än noll.

Resultatet av den här kodgenomgången är alltså kod som är tydligare, renare, har ett par färre buggar, och som dessutom är aningen snabbare. Det är inte utan att jag känner mig tämligen nöjd.

February 11th, 2013 Posted by Daniel Brahneborg | blogg | 2 comments

|