<?xml version="1.0" encoding="utf-8"?>
        <?xml-stylesheet type="text/css" href="http://www.atcomputing.nl/blog/"?>
<rss version="2.0"
 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 xmlns:dc="http://purl.org/dc/elements/1.1/"
 xmlns:admin="http://webns.net/mvcb/"
 xmlns:atom="http://www.w3.org/2005/Atom"
>
<channel>
<title>AT Blog</title>
<atom:link href="http://www.atcomputing.nl/blog/rss.xml" rel="self" type="application/rss+xml" />
<link>http://www.atcomputing.nl/blog</link>
<description>Linux/UNIXperts, opleiders &amp; oplossers</description>
<dc:language>en-us</dc:language>
<dc:creator>jc</dc:creator>
<dc:date>2010-08-31T16:37:17+02:00</dc:date>
<admin:generatorAgent rdf:resource="http://nanoblogger.sourceforge.net" />

<item>
<link>http://www.atcomputing.nl/blog/archives/2010/08/index.php#e2010-08-17T14_45_17.txt</link>
<guid isPermaLink="true">http://www.atcomputing.nl/blog/archives/2010/08/index.php#e2010-08-17T14_45_17.txt</guid>
<title>Rapperswil 2-7 augustus 2010: een C++ vergadering</title>
<dc:date>2010-08-17T14:45:17+02:00</dc:date>
<dc:creator>jc</dc:creator>
<dc:subject> Programmeren</dc:subject>
<description><![CDATA[<p>Van 2 tot en met 7 augustus werd in Rapperswil, Zwitserland 
vergaderd door de ISO C++ standaardisatiecommissie.  Ik was daar
voor Nederland als Head of Delegation aanwezig namens het NeN voor AT
Computing en de NLUUG.</p>

<p>Je kunt je afvragen waar in zo'n vergadering over gesproken wordt.
Waarom moet er zo lang gepraat worden?</p>

<p>In de vergadering in Rapperswil waren 8 landen vertegenwoordigd door
zo'n 60 mensen.  Het grootste blok komt traditiegetrouw uit de
verenigde staten.  Ieder land heeft tijdens de stemmingen één stem.
Daarom wordt tijdens de officiele zittingen altijd eerst over de
Amerikaanse mening gestemd, waarna ieder "head of delegation" van de
landen hun nationale stem mag uitbrengen.  De leden van de
standaardisatiecommissie werken bij vendors (compilerleveranciers
zoals Microsoft, HP, Sun, RedHat (g++) en EDG (Edison Design Group een
OEM compilerbouwer), C++ grootgebruikers zoals Bloomberg en Fermilabs,
onderwijsinstellingen zoals universiteiten (en AT Computing!) en vele
andere bedrijven en instellingen.  Al deze mensen hebben belang bij een
standaard die implementeerbaar is, bruikbaar is en onderwijsbaar is.
Er zijn dus verschillende belangen, waardoor soms ook spannende
discussies kunnen ontstaan.</p>

<p>De eerste C++ standaard (C++98) kwam uit in 1998.  Daar is toen lang
aan gewerkt.  In 2003 kwam er een "tussenversie" waarin kleine
onvolkomenheden werden gerepareerd, maar geen grote features
werden toegevoegd.  Er zit nu een echt nieuwe standaard aan te komen.  De
werktitel is C++0X hoewel de standaard niet in het eerste decennium
van deze eeuw uit is gekomen.  Er is vertraging opgelopen omdat op een
vrij laat moment een groot feature uit de taal is verwijderd dat maar
niet goed gespecifieerd leek te kunnen worden ("concepts").  Een reële
datum lijkt nu medio 2011 te zijn.</p>

<p>Geen enkel commissielid kent de hele standaard, of begrijpt van alle
ontwerp-beslissingen waarom die genomen zijn.  Daarom wordt de
vergadering opgesplitst in werkgroepen:  Eén of twee werkgroepen over
de bibliotheek, Eén of twee over de kern van de taal ("core") en een
over concurrency (een van de nieuwe features in de taal is
multi-threading).  De werkgroepen lopen de voor hun relevante
problemen door en doen dan een voorstel voor de gehele vergadering.</p>

<p>Hoewel de atmosfeer ontspannen en vriendschappelijk is, zijn met name
de ISO zittingen erg officieel.  Er mag absoluut niet de
indruk ontstaan dat bedrijven hun mening proberen door te drukken of
dat er gefraudeerd wordt.  Tijdens de werkgroepvergaderingen wordt er
ook regelmatig gestemd, maar dan veel informeler.  Vaak worden er
"straw polls" gehouden waar alle deelnemers aan de commissie mogen
stemmen: "Stel dat we dit probleem oplossen op de
besproken manier, zou je daar dan voor of tegen zijn".  Meestal kun je
kiezen uit vier standpunten: Strongly in Favor, Weakly in Favor,
Weakly Against, Strongly Against.  Afgekort tot SF, WF, WA en SA.
Heel soms kun je ook stemmen voor OMDB ("over my dead body"), maar dat
kwam tijdens deze vergadering niet voor.  Vaak gaat het dan over
nieuwe features, maar omdat in het stadium waarin de commissie nu zit
geen nieuwe features worden toegevoegd, speelt dat nu niet.</p>

<p>De commissie heeft een werkdocument dat in feite de standaard in
aanbouw is.  In juni 2010 hebben de leden van de ISO (de nationale
standaardisatiecommissies)  mogen stemmen over de toenmalige versie
van dat document.  Dat document had de status FCD - Final Committee
Draft.  De landen hadden de gelegenheid de FCD goed of af te keuren,
en ze konden daarbij commentaar op de tekst zelf leveren.  Dat
commentaar kan groot zijn (wij vinden dat feature XYZ verwijderd moet
worden: veel consequenties) maar ook erg klein: "op pagina 123 staat
dat de libraryfunctie zusenmezo een <code>int</code> teruggeeft, terwijl op pagina
456 een voorbeeld staat waarin de functie een <code>long</code> teruggeeft".  Deze
commentaren heten "National Body Comments".  Tijdens de vergadering in
Rapperswil zijn alle national body comments langsgelopen.  Dat waren
er honderden.  </p>

<p>De verwachting is dat over twee vergaderingen (in maart 2011) alle
commentaren adequaat kunnen zijn afgehandeld en dat het werkdocument
met de status FDIS (Final Draft International Standard) naar het ISO
gestuurd zal worden.  De landen kunnen er dan nog een laatste Ja of
Nee tegen zeggen en hopelijk hebben we dan medio 2011 een C++11
standaard.  Deze standaard maakt C++ makkelijker te gebruiken en
makkelijker te leren.</p>

<p>Daarna blijft de commissie tweemaal per jaar bijeenkomen.  Ten eerste
zullen er toch weer problemen opduiken in het 1200 pagina's tellende
document.  Maar ook zal een werkgroep weer nieuw leven ingeblazen
worden: evolution.  In deze werkgroep worden nieuwe features
voorbereid voor de standaard na 2011.  Hopelijk zal het mogelijk
blijken om het belangrijke onderwerp "concepts" dat helaas niet in de
huidige FCD meegenomen kon worden nu wél op een goede manier n de
standaard opgenomen kan worden.  Het zal foutmeldingen van compilers
een stuk beter maken, en het programmeren in C++ nog verder
vergemakkelijken.</p>

<p>In een volgende blog gaan we kijken welke spannende features er in
C++0X zitten en in hoeverre deze nu al bruikbaar zijn.</p>]]></description>

</item>
<item>
<link>http://www.atcomputing.nl/blog/archives/2010/07/index.php#e2010-07-12T12_07_11.txt</link>
<guid isPermaLink="true">http://www.atcomputing.nl/blog/archives/2010/07/index.php#e2010-07-12T12_07_11.txt</guid>
<title>Op zoek naar de bron van de datacorruptie...</title>
<dc:date>2010-07-12T12:07:11+02:00</dc:date>
<dc:creator>jc</dc:creator>
<dc:subject> Systeembeheer</dc:subject>
<description><![CDATA[<p>Thuis gebruik ik al jaren met veel plezier mythtv
(<a href="http://www.mythtv.org">http://www.mythtv.org</a>) op een Linux systeem als videorecorder.
We kijken bijna nooit meer live TV, het meest recente journaal staat
altijd klaar, en reclames spoelen we zo door omdat mythtv die voor ons 
markeert.</p>

<p>Op een zeker moment begonnen nieuwe opnamen her en der "blokjes" te
vertonen.  Van die typische MPEG-artefacten die je ook wel eens bij
satelliet-TV ziet als het weer erg slecht is, of bij DVB-T
(Digitenne en dergelijke) als een slecht afgeschermde brommer voorbij
komt rijden.</p>

<p>Toen ik als test een aantal video-files naar een andere machine
kopieerde om dáár het bestand beter te bekijken, gaf <code>rsync</code> af en
toe foutmeldingen dat het kopieren niet goed gelukt was: de checksum
aan beide zijden was niet hetzelfde.</p>

<p>Pardon?</p>

<p>Er leek dus iets mis met de hardware.  Het geheugen is het makkelijkst te
testen.  Ik heb memtest86 (<a href="http://www.memtest86.com/">http://www.memtest86.com/</a>) een dag laten draaien
om het geheugen te testen.  Geen enkel probleem.</p>

<p>Dan de disk.  In de logfiles was niets te zien.  Het viel het me wel op
(bij de vele keren rebooten) dat er af en toe een filesystem op de
disk corrupt was.  <code>smartctl</code> wist me te vertellen dat de disk zelf
niet het idee had dat hij "bijna defect" zou zijn.  En bij het
controleren van de disk gekoppeld aan een ander systeem vond ook
<code>badblocks</code> dat er niets aan de hand was.  Ook andere intensieve tests
op de disk aan een ander systeem lieten geen disk-problemen zien.</p>

<p>Vreemd.</p>

<p>De laatste test die ik deed gaf uitsluitsel.  Ik schreef een klein
programmaatje dat willekeurige bytes in een file zet:</p>

<pre><code>#include &lt;stdlib.h&gt;
#include &lt;stdio.h&gt;

int main() {

  srandom(0);

  const int n=1024;

  short ar[n];
  long long i;
  int idx;

  for (i=0, idx=0; i&lt;2L*1024*1024*1024 / sizeof(ar[0]); ++i) {
    ar[idx++]=random();
    if (idx==n) {
      write(1, ar, sizeof(ar));
      idx=0;
    }

  }
}
</code></pre>

<p>Dit programma schrijft 2GiB aan pseudo-willekeurige bytes naar
standard output.  Die gegenereerde random getallen zijn voorspelbaar
als je het begingetal kent.  Ze zijn niet goed genoeg voor
cryptografische doeleinden, maar wel goed genoeg om reproduceerbaar,
min of meer willekeurige bytes te maken.  Iedere keer dat het
programma start, krijg je bij hetzelfde begingetal dezelfde reeks.
Dat begingetal wordt gezet met de functie <code>srandom</code>.  Hier gebruiken
we het begingetal 0.  De 2GiB aan gegenereerde data kun je vervolgens
in een file zetten:</p>

<pre><code>    ./randfile &gt; OUT
</code></pre>

<p>Door het programma nogmaals te draaien (met hetzelfde begingetal 0),
kun je vergelijken of wat er in de file staat, overeenkomt met wat er
door het programma wordt geschreven:</p>

<pre><code>    ./randfile | cmp OUT -
</code></pre>

<p>En wat bleek?  In ongeveer één op de miljoen bytes, was het meest
significante bit (met de waarde 128) van 1 op 0 gevallen (zo werd een
byte met de hexadecimale waarde C3 nu 43, en een byte met de waarde F9
werd 79).  Het leek dat bij het schrijven naar de disk, soms de data
niet goed bij de disk aankwam.  Het probleem zat niet in het lezen:
nadat ik de file gemaakt had, en het programma op een andere machine
startte, bleken de bytes ook echt fout op de disk te staan.  Ook met
andere disks trad dit probleem op.  En op een andere machine met
dezelfde disk, trad het verschijnsel niet op.  Het probleem leek dus
niet aan de disk zelf te liggen.</p>

<p>Het probleem werd opgelost door een ander moederbord te nemen, en daar
de videorecorder weer mee op te bouwen.  Toen dat gebeurd was, waren
nieuwe opnamen weer vrij van blokjes, en traden filesystem problemen
niet meer op.</p>]]></description>

</item>
<item>
<link>http://www.atcomputing.nl/blog/archives/2010/06/index.php#e2010-06-24T10_48_22.txt</link>
<guid isPermaLink="true">http://www.atcomputing.nl/blog/archives/2010/06/index.php#e2010-06-24T10_48_22.txt</guid>
<title>Performance-problemen oplossen zonder extra hardware</title>
<dc:date>2010-06-24T10:48:22+02:00</dc:date>
<dc:creator>jc</dc:creator>
<dc:subject> Systeembeheer</dc:subject>
<description><![CDATA[<p>Laatst was een van onze consultants bij een klant die last had van
ernstige performance-problemen.  Na uitbreiding van het aantal
clientprocessen was het systeem bij tijd en wijle onbruikbaar traag.</p>

<p>In dergelijke gevallen komt dan al snel het aloude programma top om de
hoek zetten om te kijken waar het probleem nu werkelijk in zit.</p>

<p>Helaas laat top op systeemniveau niet zien hoe het resourcegebruik is
van disk en netwerk; dus wachtsituaties die bij die resources ontstaan
zijn niet te zien.  Gelukkig laat atop deze wel zien.  </p>

<p>Na installatie van atop bleek dat van de 24 CPUs er hooguit 6 werk
hadden, en er 18 idle waren.  De CPU-belasting kon dus niet het
probleem zijn.  Het beschikbare geheugen (48GiB) werd voor 70% benut
door de cache en er was geen swapping actief.  Geheugengebrek was dus
ook niet de oorzaak van het performance-probleem.  Ook de disks konden
de vraag nog aan:  ze waren gemiddeld 30% busy.  En het netwerk
(gigabit) kon de stroom data van circa 10 megabit/s ruimschoots aan.</p>

<p>Je kunt je natuurlijk afvragen: hoe kan een systeem waarvan deze vier
hardware resources niet overbelast zijn, toch zo onwerkbaar traag
zijn.  Vaak denken mensen dat als een systeem te traag is, er door
extra of snellere hardware verlichting verkregen kan worden.  Helaas
is dat niet altijd (of eigenlijk: meestal niet) het geval.</p>

<p>Laten we een voorbeeld nemen.  Stel dat er tegelijkertijd 100
applicaties draaien op het systeem die allemaal een record in een
bestand willen aanpassen.  Ze zullen daarvoor een record lock
aanvragen bij het systeem.  Als ze allemaal een ander record willen
aanpassen, kunnen ze tegelijkertijd op het bestand werken.  Maar wat
nu als de applicatie zo geschreven is dat (om welke reden dan ook) er
geen lock wordt aangevraagd voor een enkel record, maar voor de gehele
file?  Dan kunnen de 100 applicaties niet meer tegelijkertijd de file
aanpassen, en zullen ze een voor een aan het werk moeten gaan.  Zelfs
al heb je 24 CPUs en zou je dus 24 applicaties echt tegelijk kunnen
afhandelen.  Als er nu per seconde meer nieuwe aanvragen binnenkomen
dan er een-voor-een per seconde kunnen worden afgehandeld, krijg je een
steeds groter wordende achterstand.  </p>

<p>Je kunt dit vergelijken met een 24-baans snelweg waarvan
op een klein stukje van de weg maar één baan mag worden bereden.
Als je het brede deel van de snelweg verbreedt van 24 rijstroken naar
48, levert dat helemaal niets op.  Het is de wegversmalling die het
probleem oplevert.  Hier geldt: meer asfalt, niets wijzer.  Als je de
wegversmalling zou verbreden (waarschijnlijk een veel goedkopere
oplossing dan de hele weg verbreden), heb je het probleem op een veel
effectievere manier opgelost.</p>

<p>In de praktijk blijkt dat software resources (zoals locks en
semaforen) erg vaak de reden zijn van performance-problemen.
Dergelijke wachtsituaties kun je zien door te kijken naar het
wait-channel in de uitvoer van <code>ps</code>:</p>

<pre><code>$ ps -o pid,wchan:20,s,cmd
  PID WCHAN                S CMD
32275 wait                 S -bash
32277 pipe_wait            S awk {n+=$0} END {print n}
32278 fcntl_setlk          S tstlock
32279 -                    R tstlock
32280 fcntl_setlk          S tstlock
32281 fcntl_setlk          S tstlock
32282 fcntl_setlk          S tstlock
32283 fcntl_setlk          S tstlock
32284 fcntl_setlk          S tstlock
32285 fcntl_setlk          S tstlock
32299 -                    R ps -o pid,wchan:20,s,cmd
</code></pre>

<p>We kunnen hier duidelijk zien dat er maar een tstlock proces is dat
loopt (de status is R).  De andere staan allemaal te wachten op het
verkrijgen van een file lock (ze staan in de kernel te wachten op
fcntl_setlk).  Op een systeem met meerdere CPUs staan hier de andere
CPUs duimen te draaien...</p>

<p>Moraal van dit verhaal: Kijk bij performance-problemen naar alle
resources, en niet alleen naar de hardware resources.  Voor je
het weet heb je veel geld uitgegeven aan nieuwe hardware die het
probleem niet oplost omdat de problemen in de applicatiesoftware
zitten.  Motto: extra ijzer, niets wijzer.  En oh ja, bij die klant
kwam het ook goed zonder andere hardware te hoeven kopen.</p>]]></description>

</item>
<item>
<link>http://www.atcomputing.nl/blog/archives/2010/06/index.php#e2010-06-15T16_17_28.txt</link>
<guid isPermaLink="true">http://www.atcomputing.nl/blog/archives/2010/06/index.php#e2010-06-15T16_17_28.txt</guid>
<title>To bash or to ksh, what is the difference?</title>
<dc:date>2010-06-15T16:17:28+02:00</dc:date>
<dc:creator>hjt</dc:creator>
<dc:subject> Programmeren</dc:subject>
<description><![CDATA[<p>In onze Linux/UNIX deel 2 cursus gaat het over shell scripting,
met awk en sed daarbij behandeld.
We mikken niet op allerlei exotische shells, maar gaan
ervan uit dat men op commerciele UNIX'en (Solaris,
HP/UX, AIX) een ksh gebruikt, en bij Linux en ..BSD een bash.
Toch willen we niet dat de cursus vol gaat zitten met
"bij ksh doe je het zus, en
bij bash doe je het zo". Er zijn best veel detailverschillen
tussen die twee shells, maar die zitten gelukkig bijna allemaal
in de categorie "speelgoed voor gevorderden". Je kunt flinke
scripts bouwen zonder dat verschillen de kop opsteken.</p>

<p>Dus als je een keer een script moet porten van een ksh-omgeving
naar een bash-omgeving, of omgekeerd, dan zou aanpassen van de
''#!/bin/ksh'' versus ''#!/bin/bash'' voldoende kunnen zijn</p>

<p>Het enige verschil waar we in bovengenoemde cursus op wijzen
is het gedrag van ''echo'' wanneer je daarbij de regelovergang
wilt onderdrukken:</p>

<pre><code>echo 'Maak nu uw keuze: \c'     # Posix-standaard
echo -n 'Maak nu uw keuze: '    # Berkeley-stijl
</code></pre>

<p>De Berkeley-versie is het meest verbreid, maar ze is niet conform
de Posix standaard. Bij Linux zit die Berkeley-vorm zowel in de
''/bin/echo'' executable als in de built-in echo van ''bash''.
De Posix-standaard vorm komt uit de Korn Shell, maar vind je daar
momenteel niet meer in terug.</p>

<p>Dave Korn vertelde een keer dat hij zoveel mails kreeg dat zijn shell
het fout deed, dat hij maar is opgehouden met te antwoorden
dat de fout bij de andere partij zit omdat die van de standaard
afwijkt. Dus in de huidige ''ksh'' werkt de built-in ''echo'' ook op
de ''-n'' manier. Maar als je een mix van oudere en jongere
ksh versies in je computerverzameling hebt, dan kan dat nog wel
portabliteitsproblemen veroorzaken. De beste oplossing is om,
als je speciale effecten in je ''echo'' wilt hebben, helemaal
geen ''echo'' commando te gebruiken maar over te stappen op ''printf''.
Echo blijft het gemakkelijkste werkpaard voor recht toe recht aan
boodschappen.</p>

<p>In de praktijk ben ik ook geregeld gestoten op een ander gemeen
verschil tussen ksh en bash, dat zit in de afhandeling van pipes:</p>

<pre><code>commando1 | commando2
</code></pre>

<p>De ''bash'' zal het laatste commando in deze reeks uitvoeren in een soort
"sub-shell" context, vergelijkbaar met wat ronde haakjes in shell-taal
doen, en wat in shell-functies gebeurt. De ''ksh'' voert (in dit geval)
het laatste commando uit in de context van de hoofd-shell van het script.
Het verschil tussen die twee manieren merk je alleen als dat laatste
commando in de shell een blijvende verandering maakt, zoals vullen van
een shell-variabele:</p>

<pre><code>VAR=tekst1
echo tekst2 | read VAR
echo $VAR;  # ksh geeft nieuwe tekst2, bash geeft oude text1
</code></pre>

<p>Als je een shell-variabele wilt vullen met een brokje informatie
dat gebouwd moet worden door een pipe line van commando's dan kun je
dus beter een alternatief kiezen dat in beide shells werkt:</p>

<pre><code>VAR=$(commando | ...)
</code></pre>

<p>Het is een erg handige construtie om na de pipe een ''while''-loop
te gebruiken:</p>

<pre><code>VAR=tekst1; LINECOUNT=0
echo 'tekst2
tekst3' | while read VAR; do
    let LINECOUNT++
    echo VAR in de loop is $VAR
done 
echo $LINECOUNT regels gehad en VAR na afloop is $VAR
</code></pre>

<p>Binnen de loop gebeurt wat je verwacht, en in beide shells
hetzelfde: achtereenvolgens zie je in de loop de ''tekst2''
en ''tekst3'' worden afgedrukt. Maar na afloop meldt ''bash''
dat VAR ''tekst1'' bevat. Dus die is terug naar de waarde van
v&oacute;&oacute;r de loop, en consistent met het vorige voorbeeld.
''Bash'' meldt ook een LINECOUNT van 0.
''Ksh'' meldt dat de VAR leeg eindigt.
Dat komt omdat de laatste ''read VAR'' niks meer binnenkreeg,
en daardoor eindigde de loop.
De LINECOUNT is bij ''ksh'' 2, zoals het hoort.</p>

<p>De moraal blijft dus: let op bij een pipeline die een blijvend
resultaat binnen de shell-toestand moet opleveren.</p>]]></description>

</item>
<item>
<link>http://www.atcomputing.nl/blog/archives/2010/05/index.php#e2010-05-12T15_38_12.txt</link>
<guid isPermaLink="true">http://www.atcomputing.nl/blog/archives/2010/05/index.php#e2010-05-12T15_38_12.txt</guid>
<title>Mag het een bitje meer zijn?</title>
<dc:date>2010-05-12T15:38:12+02:00</dc:date>
<dc:creator>jacco</dc:creator>
<dc:subject> Systeembeheer, Whitepaper</dc:subject>
<description><![CDATA[<p>Hoeveel bytes bevat een kilobyte?<br/>
Volgens sommigen: 1000. Volgens anderen: 1024. Weer anderen zeggen: "Het hangt er van af aan wie je het vraagt."<br/> 
Er is spraakverwarring over en dat komt de duidelijkheid en transparantie niet ten goede.</p>

<p>Het SI-voorvoegsel (Uit het Frans: Syst&egrave;me International d'unit&eacute;s) kilo staat voor 1000 (x 10<sup>3</sup>). Mega staat voor x1000x1000 (x 10<sup>6</sup>). Daarna komen nog giga (x 10<sup>9</sup>), tera (x 10<sup>12</sup>), peta (x 10<sup>15</sup>), exa (x 10<sup>18</sup>), zetta (x 10<sup>21</sup>) en yotta (x 10<sup>24</sup>).<br/>
Computers werken met bits (binair, 0 of 1: 2 mogelijkheden) en rekenen daardoor makkelijker met grootheden van 2 tot de macht X. Het getal 1000 wordt daarbij het dichtst benaderd door 2<sup>10</sup> (= 1024).<br/>
Toen het computeren nog in de kinderschoenen stond, werden de SI-voorvoegsels misbruikt voor veelvouden van 1024. Bijvoorbeeld: 1 kilobyte = 2<sup>10</sup> bytes = 1024 bytes. 1 megabyte is dan 1024 kilobyte.<br/>
Handig in het gebruik en het verschil is ook niet zo groot.</p>

<p>Hoe worden de voorvoegsels in de praktijk gebruikt?</p>

<ul>
<li><p>Een kachel van 2 kilowatt heeft een vermogen van 2.000 watt.</p></li>
<li><p>De afstand tussen Utrecht en Nijmegen is 85 kilometer, dat is dus 85.000 meter.</p></li>
<li><p>Deze foto kost 2 megabyte. Dat is 2 x 1024 x 1024 = 2.097.152 bytes (en niet 2.000.000).</p></li>
<li><p>Via mijn ADSL-aansluiting kan ik downloaden met een snelheid van maximaal 8 megabit per seconde. Dat is 8.000.000 bits per seconde.</p></li>
<li><p>Op mijn nieuwe harddisk past 1 terabyte, ofwel 1000 gigabyte, ofwel 1.000.000.000.000 bytes.</p></li>
<li><p>Op een 3,5" floppy past 1,44 megabyte. Dat is niet 1,44 x 1000 x 1000 bytes, ook niet 1,44 x 1024 x 1024, maar wel 1,44 x 1000 x 1024 (= 1.474.560).<br/> Namelijk: 2 koppen x 80 cylinders x 18 sectoren x 512 bytes = 1.474.560.</p></li>
</ul>

<p>Dit was niet tot ieders tevredenheid: afgezien van het feit dat er spraakverwarring optreedt, is 1000 gewoon niet gelijk aan 1024.<br/>
En is het verschil bij kilo nog niet zo groot (2,4%), bij tera (1000<sup>4</sup> t.o.v. 1024<sup>4</sup>) is het verschil al opgelopen tot 9,95%. Om nog maar niet te spreken over yotta (20,89%).</p>

<p>Daarnaast is er ook een probleem met afkortingen: Het is niet duidelijk of met de afkorting kb nu kilobyte of kilobit wordt bedoeld. Een (stilzwijgende) afspraak is dat voor bit een kleine b wordt gebruikt, en voor byte een grote B.</p>

<p>Verwarring alom.<br/>
De International Electrotechnical Commission (IEC) heeft zich de kritiek aangetrokken. In 1998 is een standaard ingevoerd die orde in de chaos heeft geschapen door een handvol nieuwe voorvoegsels (prefixen) te introduceren. Deze IEC-prefixen verwijzen naar binaire veelvouden: kibi (2<sup>10</sup>), mebi (2<sup>20</sup>), gibi (2<sup>30</sup>), enz.</p>

<table border="1" cellpadding="3" cellspacing="3">
    <tr align="center">
        <th colspan="3">Decimale veelvouden volgens SI (1000<sup>X</sup>)</th>
        <th></th>
        <th colspan="3">Binaire veelvouden volgens IEC (1024<sup>X</sup>)</th>
    </tr>
    <tr align="center">
        <th>Macht</th>
        <th>Afko</th>
        <th>Prefix</th>
        <th>Verschil</th>
        <th>Prefix</th>
        <th>Afko</th>
        <th>Macht</th>
    </tr>
    <tr align="center">
        <td>10<sup>3</sup> (1000<sup>1</sup>)</td>
        <td>k</td>
        <td>kilo</td>
        <td>2%</td>
        <td>kibi</td>
        <td>Ki</td>
        <td>2<sup>10</sup> (1024<sup>1</sup>)</td>
    </tr>
    <tr align="center">
        <td>10<sup>6</sup> (1000<sup>2</sup>)</td>
        <td>M</td>
        <td>mega</td>
        <td>5%</td>
        <td>mebi</td>
        <td>Mi</td>
        <td>2<sup>20</sup> (1024<sup>2</sup>)</td>
    </tr>
    <tr align="center">
        <td>10<sup>9</sup> (1000<sup>3</sup>)</td>
        <td>G</td>
        <td>giga</td>
        <td>7%</td>
        <td>gibi</td>
        <td>Gi</td>
        <td>2<sup>30</sup> (1024<sup>3</sup>)</td>
    </tr>
    <tr align="center">
        <td>10<sup>12</sup> (1000<sup>4</sup>)</td>
        <td>T</td>
        <td>tera</td>
        <td>10%</td>
        <td>tebi</td>
        <td>Ti</td>
        <td>2<sup>40</sup> (1024<sup>4</sup>)</td>
    </tr>
    <tr align="center">
        <td>10<sup>15</sup> (1000<sup>5</sup>)</td>
        <td>P</td>
        <td>peta</td>
        <td>13%</td>
        <td>pebi</td>
        <td>Pi</td>
        <td>2<sup>50</sup> (1024<sup>5</sup>)</td>
    </tr>
    <tr align="center">
        <td>10<sup>18</sup> (1000<sup>6</sup>)</td>
        <td>E</td>
        <td>exa</td>
        <td>15%</td>
        <td>exbi</td>
        <td>Ei</td>
        <td>2<sup>60</sup> (1024<sup>6</sup>)</td>
    </tr>
    <tr align="center">
        <td>10<sup>21</sup> (1000<sup>7</sup>)</td>
        <td>Z</td>
        <td>zetta</td>
        <td>18%</td>
        <td>zebi</td>
        <td>Zi</td>
        <td>2<sup>70</sup> (1024<sup>7</sup>)</td>
    </tr>
    <tr align="center">
        <td>10<sup>24</sup> (1000<sup>8</sup>)</td>
        <td>Y</td>
        <td>yotta</td>
        <td>21%</td>
        <td>yobi</td>
        <td>Yi</td>
        <td>2<sup>80</sup> (1024<sup>8</sup>)</td>
    </tr>
</table>

<p>Storagefabrikanten - zoals producenten van harddisks - rekenen in megabytes en terabytes, dus in 1000-tallen. Dat zorgt nogal eens voor een stukje consumentenfrustratie: "Ik heb een nieuwe harddisk gekocht, maar die blijkt veel kleiner te zijn dan op de verpakking wordt aangegeven!"<br/>
Als je een harddisk van 1 terabyte (1 TB) koopt, krijg je een rauwe opslagcapaciteit van 1.000.000.000.000 bytes, ofwel 90.95% van de grootte die je verwacht als je ervan uitgaat dat 1 kilobyte 1024 bytes bevat. En daar gaat dan nog wat snijverlies en overhead van af omdat er partities en/of filesystemen gemaakt moeten worden.<br/></p>

<p>De zebibyte (ZiB) en yobibyte (YiB) zijn momenteel nog niet daadwerkelijk in gebruik als je het over opslagcapaciteit hebt. Om een YiB aan informatie op te slaan op 3,5" floppies (wie gebruikt ze nog?), heb je een stapel floppies nodig ter grootte van 2 maal de afstand van de aarde naar de zon.</p>

<p>Meer leesvoer:</p>

<ul>
<li><p><a href="http://physics.nist.gov/cuu/Units/binary.html">http://physics.nist.gov/cuu/Units/binary.html</a></p></li>
<li><p><a href="http://en.wikipedia.org/wiki/Binary_prefix">http://en.wikipedia.org/wiki/Binary_prefix</a></p></li>
</ul>]]></description>

</item>
<item>
<link>http://www.atcomputing.nl/blog/archives/2010/05/index.php#e2010-05-04T12_59_53.txt</link>
<guid isPermaLink="true">http://www.atcomputing.nl/blog/archives/2010/05/index.php#e2010-05-04T12_59_53.txt</guid>
<title>Rsync en performance</title>
<dc:date>2010-05-04T12:59:53+02:00</dc:date>
<dc:creator>jc</dc:creator>
<dc:subject> Tips and Tricks</dc:subject>
<description><![CDATA[<p>Het Probleem</p>

<p>Ik was laatst een nieuwe disk aan het in-gebruik-nemen, waarbij ik
veel grote files moest kopieren van de oude (kleinere) disk.  Dat
soort dingen doe ik normaliter met rsync (daarvan zitten de opties in
mijn vingers).  Maar nu zag ik dat de performance bedroevend was.  Ik
moest zo'n 700GiB aan data overpompen, en dat ging met een tempo van
zo'n 30MiB/s.  Beide disks kunnen ieder minstens 100MiB/sec lezen
en/of schrijven.  En ik haalde dus nog geen derde daarvan.  (We hebben
het over een totale doorlooptijd van ruim 2 uur versus 6 uur!)</p>

<p>Meten...</p>

<p>Ik ben toen eens wat gaan meten en gaan spelen.  Hiervoor bedacht ik
een simpele benchmark: het kopieren van een file van 10GiB.  Zowel de
bronfile als de bestemming stonden op een ext4 filesysteem dat ik op
de buitenste (snelste) tracks van de twee fysieke disk formatteerde.</p>

<p>Als kopieerprogramma's gebruikte ik:</p>

<ul>
<li>rsync</li>
<li>cpio</li>
<li>cp</li>
<li>cat</li>
</ul>

<p>uiteraard voorafgegaan door commando's om de cache te legen en
afgesloten door een sync om de cache te flushen.  Dus bijvoorbeeld:</p>

<pre><code>sync                               # forceer dirty buffers naar disk
echo 3 &gt; /proc/sys/vm/drop_caches  # gooi caches weg
time sh -c "cp $SRC $DEST; sync"   # meet cp én sync tijd
</code></pre>

<p>De echo naar /proc/sys/vm/drop_caches forceert het volledig
invalideren van de cache.  Dat werkt alleen voor de niet-dirty
pagina's, daarom dat we eerst een sync commando gebruiken om alle
dirty pagina's naar disk te forceren.  Het betreffende kopieer programma zal
de 10GiB file kopieren, maar ook eindigen vóórdat de laatste blokken naar
disk worden geschreven (die blijven nog 30 seconden in de cache plakken).
Daarom meet het time commando de tijd die nodig is voor het kopieer programma,
en de sync om de laatste restjes te flushen.</p>

<p>De metingen voor rsync, cpio, cp en cat wezen het volgende uit:</p>

<pre><code>user      sys     elaps  hog  MiB/s  test
158.75   108.71  320.08  83%   31   rsync
  5.24    80.60  116.81  73%   87   cpio
  0.73    53.41  109.36  49%   93   cp
  1.42    62.03  108.80  58%   94   cat
</code></pre>

<p>Ik ben daarop wat nader gaan kijken.  Wat blijkt:
rsync heeft 3 processen nodig:  een lezend, een schrijvend en een
niets-doend (control?) proces.  Op mijn 4-core systeem draaien ze
bijna de hele tijd alledrie op één enkele CPU.  Met het commando
taskset kun je afdwingen op welke CPUs een proces mag draaien.  Stel
dat de drie rsync processen de PID's 1111, 1112, 1113 hebben, dan kun
je afdwingen dat ze ieder op hun eigen CPU draaien met:</p>

<pre><code>taskset -pc 0 1111   # forceer op CPU0
taskset -pc 1 1112   # forceer op CPU1
taskset -pc 2 1113   # forceer op CPU2
</code></pre>

<p>Door met het taskset commando meteen na het starten ieder rsync
proces zijn eigen CPU te geven, gaat de throughput omhoog van
31MiB/s naar 39MiB/s.  Als de drie rsync processen met taskset
geforceerd worden op één CPU, loopt de performance terug naar
27MiB/s.</p>

<p>rsync heeft daarnaast erg veel user+sys CPU-tijd nodig.  Ondanks dat, schaalt
de on-demand cpufreq niet de frequentie omhoog.  Als ik de frequentie forceer
op de hoogst mogelijke met:</p>

<pre><code>for i in 0 1 2 3 ; do
  echo performance &gt; /sys/devices/system/cpu/cpu$i/cpufreq/scaling_governor
done
</code></pre>

<p>dan gaat de throughput van rsync fors omhoog: 58MiB/sec.  De CPU's draaien dan
op de hoogst mogelijke frequentie (op dit systeem 2.6GHz).  Als we dat
combineren met de taskset truuk om de drie rsync processen ieder hun eigen CPU
te geven, weten we er zelfs 79MiB/s uit te persen.  Nog altijd zo'n 20% lager
dan de andere, en tegen flink CPU-verbruik, maar toch.</p>

<p>De conclusie is dat je door in plaats van rsync, cp te gebruiken in de default
instellingen, je van 30MiB/s naar 93MiB/s kan gaan.  Maar ook door een
beetje te spelen met de instellingen van het systeem, kun je zelfs met
rsync flinke verbeteringen bereiken!</p>

<pre><code>throughput #CPUs    frequentie
22MiB/s    1 CPU,    0.8GHz
23MiB/s    1-3 CPUs, 0.8GHz
27MiB/s    1 CPU,    ondemand
31MiB/s    1-3 CPUs, ondemand     &lt;&lt; default
38MiB/s    3 CPUs,   0.8GHz
39MiB/s    3 CPUs,   ondemand
58MiB/s    1-3 CPUs, 2.6GHz
58MiB/s    1 CPU,    2.6GHz
79MiB/s    3 CPUs,   2.6GHz
</code></pre>

<p>In de tabel geeft de tweede kolom aan hoe de rsync processen over de
CPU's verdeeld werden: 1 CPU wil zeggen dat met taskset de drie rsyncs
alledrie op één en dezelfde CPU gedwongen werden.  1-3 CPus wil zeggen
dat de scheduler zelf de vrije hand had, en 3 CPU's wil zeggen dat
ieder rscyn proces met taskset zijn eigen CPU kreeg.</p>

<p>Je kunt duidelijk zien dat het slechter kan dan de default
instellingen, maar ook heel veel beter!</p>

<p>De toekomst...</p>

<p>Op Linux Weekly News
<a href="http://lwn.net/Articles/384132/">http://lwn.net/Articles/384132/</a>
stond recentelijk een bericht dat de ondemand scheduler moeite heeft
met processen die veel I/O doen en ook vrij veel CPU tijd nodig
hebben: In de tijd dat de CPU op disk I/O staat te wachten, wordt de
CPU weer naar een lage frequentie teruggeschaald.  Als het proces dan
weer wil rekenen duurt het enige tijd voordat er weer omhoog wordt
geschaald.  Arjan van de Ven van het Intel Open Source Technology
Centre heeft een aanpassing op de ondemand scheduler aangebracht die
de CPU pas omlaag schaalt als deze echt idle is (en niet op disk I/O
staat te wachten).</p>

<p>Je kunt dit verschijnsel ook mooi zien:</p>

<p>Als je</p>

<pre><code>modprobe cpufreq_stats
</code></pre>

<p>doet, krijg je precies (in jiffies gemeten) het aantal hits te zien
voor de verschillende te gebruiken clock frequenties.</p>

<p>Bekijken we dit voor een rsync commando, dan zien we
bijvoorbeeld voor cpu nummer 2:</p>

<pre><code>cat /sys/devices/system/cpu/cpu2/cpufreq/stats/time_in_state
2600000 423293
1900000 363
1400000 534
800000 6645805
</code></pre>

<p>Je kunt hier dus zien dat cpu2 (sinds het laden van de module) het
grootste deel van de tijd in de lage frequentie (0.8GHz) heeft gelopen.</p>

<p>De aanpassing van Arjan van de Ven is nog een lapje voor het bloeden,
want hij heeft een hele nieuwe governor in voorbereiding.</p>]]></description>

</item>
<item>
<link>http://www.atcomputing.nl/blog/archives/2010/04/index.php#e2010-04-09T17_51_33.txt</link>
<guid isPermaLink="true">http://www.atcomputing.nl/blog/archives/2010/04/index.php#e2010-04-09T17_51_33.txt</guid>
<title>Fontnamen wijzigen in een OO document</title>
<dc:date>2010-04-09T17:51:33+02:00</dc:date>
<dc:creator>hjt</dc:creator>
<dc:subject> Systeembeheer</dc:subject>
<description><![CDATA[<p>Na mijn vorige blog over de verschillende versies van eenzelfde
font, met alle lay-out ellende van dien, gaat dit blog-verhaal
over het vervolg van de operatie.</p>

<p>We wilden meer controle over de fonts die in onze
cursusdocumentatie voorkwamen.
Helvetica (proprietary eigendom van Linotype) en
Courier (proprietary eigendom van Monotype, oorspronkelijk
voor IBM ontworpen), en Ariel en Verdana (beiden proprietary
eigendom van Microsoft) wilden we toch maar uitbannen
en vervangen door leden van de Nimbus-Sans familie,
om meer controle over de weergave te krijgen.
We wilden niet weer verrast worden door allerlei "automatische"
font-substituties zoals die plaats kunnen vinden door het
'defoma'-mechanisme (Debian Font Manager):
<a href="http://mintaka.sdsu.edu/GF/bibliog/latex/debian/defoma.html">http://mintaka.sdsu.edu/GF/bibliog/latex/debian/defoma.html</a>
of zelfs door Adobe Acroread:
<a href="http://www.creativepro.com/article/acrobat-tips-fonts-can-make-or-break-pdfs">http://www.creativepro.com/article/acrobat-tips-fonts-can-make-or-break-pdfs</a></p>

<p>Omdat .odg en .odp files XML-bestanden zijn kwamen we op het idee
om een batchmatige fontnaam-zapper te bouwen.
Dat werd een shellscript, plus een C-programma.
Het script begint als volgt:</p>

<pre><code># Pak de .od[gp] file uit en onthoud in welke volgorde
# de onderdelen als aparte files eruit kwamen:
unzip "$1" &gt; unzipsequence

ed -s unzipsequence &lt;&lt; 'Here'
1,$g/^ *Archive:/d
1,$s/^ *[^:][^:]*: *//
1,$s/  *$//
w
q
Here
</code></pre>

<p>Dit script verwacht te starten in een lege directory,
en $1 is de naam van de te bewerken .od[pg]-file
(die dus in een andere directory moet staan).
We unzippen die file, en vangen de outputboodschappen op.
Die vertellen de volgorde waarin de
onderdelen werden "ontzipt", en die volgorde willen we voor
de zekerheid later weer reconstrueren.</p>

<p>In de daaropvolgende ed-sessie met Here-document trimmen we
overbodig geachte boodschappen weg. Dat had ook wel met sed
in een pipeline achter het unzip-commando gekund.</p>

<p>Het unzippen van een .od[pg] file levert een hele boom
aan losse files en subdirectories op.
Files met naam-extensie .xml vormen de kern van deze verzameling. </p>

<p>Die gaan we allemaal langs, en geven we aan ons C-programma
dat in de scriptcode hieronder $ZAPFONTS is genoemd.
Daarover straks meer:</p>

<pre><code># Selecteer uit de fileverzameling degenen waar fontnamen
# in kunnen staan. Geef die aan het 'zapfonts' programma.
# ODF files kunnen subdirs bevatten met z.g. embedded objects.
# Deze heten "Object 1" enz. met spatie in de filenaam.
# dus de dubbelquotes om "$name" zijn essentieel.
find . -type f -name '*.xml' | while read name; do
    $ZAPFONTS "$name" &gt; tt
    mv tt "$name"
done
</code></pre>

<p>Nadat we alle fontnamen hebben vervangen, "zippen" we de
verzameling losse files weer in elkaar. We gebruiken de
output-boodschappen van de oorspronkelijke unzip om
de files weer min of meer in hun oorspronkelijke volgorde
te krijgen. Ik weet niet of dat nodig is, maar het leek me een
nuttige (en niet zo moeilijk te realiseren) voorzorgsmaatregel.</p>

<pre><code># Voeg alle onderdeel-files weer bij elkaar:
cat unzipsequence | zip -X outfile.odp -@
mv  outfile.odp  "$1"
rm -rf *
</code></pre>

<p>De opnieuw ingepakte file schrijven we over de oorspronkelijk
aangeleverde $1 filenaam heen, en we vegen de huidige directory
weer net zo schoon als hij was aan het begin van de rit.</p>

<p>Een opmerking terzijde: als je in een shellscript het
commando "rm -rf *" hebt staan, dan moet je wel heeeeel erg
zorgvuldig alle cd-commandos in het script controleren.
Want als een cd-commando mislukt dan ben je daarna met deze
'rm -rf *' goed de pineut. Gelukkig bevat het huidige script geen
enkel cd-commando omdat het aanneemt in een lege directory
te beginnen. Maar een 'if [ $(ls | wc -l) -ne 0 ]; then...'
test aan het begin is toch verstandig. In andere scripts
waarin ik zo'n 'rm -rf' moest verwerken heb ik na elk
cd-commando een controle op de output van pwd toegevoegd.</p>

<p>Blijft over om een paar woorden te wijden aan het gebruikte
ZAPFONTS C-programma. Ik zal u de source-code besparen
maar liefhebbers kunnen op verzoek een kopietje krijgen.</p>

<p>Een xml-bestand bestaat uit tekstregels, dus in eerste
instantie denk je in C aan een loopje in de trant van</p>

<pre><code>while(fgets(......) != NULL) {
zap de fontnamen in deze regel
fputs(.......)
}
</code></pre>

<p>Dit is "Standard-I/O library voor beginners".
Maar 'fgets' vereist dat je van tevoren een
maximale regellengte moet inschatten.
Toen ik wat XML-files bekeek om mijn schatting te onderbouwen
bleek dat die soms waanzinnig lange regels bevatten:
tot een paar duizend characters toe. </p>

<p>Uit mijn UNIX-jeugd, lang voordat het web bestond, herinner ik me
een tijdschrift-artikel waarvoor iemand de meestgebruikte
UNIX-commando's had getest op tekstfiles met lange regels:
van 10000 characters per stuk.
Bijna geen enkele utility overleefde (toen) de test.
Bij deze XML-files moest ik ook oppassen.</p>

<p>Ik besloot mijn C-programma geen regels te laten lezen,
maar porties van 1Kb.
Dan loop je wel het gevaar dat een fontnaam precies
over zo'n portiegrens heen ligt. Dus ik werk nu met een
array van 2Kb, en ik begin met twee porties van 1Kb data 
uit de file te lezen, en achter elkaar in het array te plaatsen.
Dan scan ik de eerste 1Kb-plus-een-paar-dozijn characters
en zap de "foute" fontnamen. Dat stuk schrijf ik weer uit.
Vervolgens schuif ik de tweede Kb in het array naar voren,
en lees er een nieuwe 1Kb portie achteraan, enz.</p>

<p>Het feit dat een nieuwe fontnaam langer of korter kan zijn
dan de oude naam die hij vervangt maakt het uitschrijven
nog een beetje gecompliceerd, maar het is al met al best
te doen. Ik heb dat in elk geval sneller zelf geprogrammeerd
dan dat ik op het Internet moet zoeken naar een programma
dat het kan, gevolgd door het beoordelen of dat wel solide
genoeg is gemaakt.</p>

<p>Nadat een .odg of .odp bestand op deze manier gezapt is,
is het verstandig om het even in OO te laden,
en door te bladeren voor visuele inspectie.
Wij vonden af en toe een plaats waar nog wat
positioneringen op een pagina gecorrigeerd moesten worden.
Maar in het algemeen is de operatie erg soepel verlopen.</p>

<p>Overigens: voor wie in typefonts is ge&iuml;nteresseerd
levert Wikipedia een schat aan informatie.
Zoek maar eens naar een van de bekende font-namen.</p>]]></description>

</item>
<item>
<link>http://www.atcomputing.nl/blog/archives/2010/04/index.php#e2010-04-01T14_09_36.txt</link>
<guid isPermaLink="true">http://www.atcomputing.nl/blog/archives/2010/04/index.php#e2010-04-01T14_09_36.txt</guid>
<title>OpenOffice font problemen </title>
<dc:date>2010-04-01T14:09:36+02:00</dc:date>
<dc:creator>hjt</dc:creator>
<dc:subject> Systeembeheer</dc:subject>
<description><![CDATA[<p>Bij ATComputing maken we het merendeel van onze cursusdocumentatie zelf.
Dat is, denken wij, een fundament onder onze goede naam,
maar ook een flinke last op onze schouders.
We zitten daarbij natuurlijk niet te wachten op problemen met de
gebruikte software.
Toch hebben we al het nodige voor onze kiezen gehad.</p>

<p>Al vele jaren geleden zijn we voor onze presentaties
(toen nog overhead-projectie) OpenOffice gaan gebruiken.
Voorheen hadden we een commercieel pakket "Island Draw"
onder Solaris gekocht.
Maar de makers werden op een gegeven moment opgekocht door een
concurrent, die het pakket daarna vakkundig de nek omdraaide.
Wij bleven zitten met honderden bestanden in proprietary formaat,
en executables die afhankelijk waren van een license key
waarin een specifiek CPU-id was verwerkt.
Gelukkig is de voormalige importeur van het pakket ons nog lang
behulpzaam geweest met "naleveren" van benodigde keys.
Maar op een gegeven moment deed ook zijn key-generator het niet meer.
Van proprietary bestandsformaten hadden we onze buik inmiddels wel vol.</p>

<p>In ons bedrijf proberen we wat UNIX- en Linux versies betreft te voorkomen
dat we eenkennig worden.
Onze medewerkers gebruiken op hun laptops en hun thuis-computers
allerlei verschillende distributies. Ook binnen het bedrijf is vari&euml;teit.
Maar bij OpenOffice "draw" bestanden merkten we keer op keer dat bij
Courier- en Nimbus fonts verschillen ontstonden: op de ene computer
tekende je een alinea tekst met een randje eromheen of pijltje ernaartoe,
en op een andere computer toonde dat bestand de tekst half buiten de rand,
en de pijl ver naast zijn doel.
Het probleem zat niet in zo'n bestand zelf:
als je dat ongewijzigd terugverhuisde naar de oorspronkelijke computer
stond alles weer netjes op z'n plaats.
Nauwkeurig kijken maakte duidelijk
dat ergens een font-metrics probleem moest zitten,
met verschillend gedefinieerde letterhoogten voor hetzelfde font.</p>

<p>Ik ben nog steeds geen expert in de vraag
waar een OpenOffice programma nu eigenlijk zijn fonts vandaan haalt.
Wel weet ik dat dat een reuzendoolhof is.
Hier speelt ook de X font server zijn rol mee.
Een commando als 'xlsfonts' meldt vaak al een paar duizend fonts.
Ons probleem werd niet alleen be&iuml;nvloed door de fontverzameling
op de lokale machine,
maar ook nog door de netwerk-wijd aangesproken fontserver.</p>

<p>Om toch ergens te beginnen zette ik 's avonds laat
onze centrale font-server maar eens stil.
Hier loert een soort Heisenberg-onzekerheid
waarbij de manier van meten een grote invloed kan uitoefenen op wat je meet.
Als OpenOffice bij afwezigheid van een fontserver een fall-back doet
naar lokale fonts, dan kijk je misschien tegen een heel nieuwe situatie aan.
Maar het probleem in de tekeningen bleef bestaan,
dus moest binnen een lokale machine te traceren zijn.
Mijn kansen stegen.</p>

<p>OpenOffice starten met een grafische muisklik is mooi, maar voor
trouble-shooting niet handig. Via het configuratie-menu'tje voor de iconen
kwam ik achter de naam van de executable file, maar dat was een shellscript.
Langs een heel spoor van shellscripts en symlinks kwam ik uiteindelijk
bij een "echte" 'soffice.bin' executable.
Maar die is slechts een paar duizend bytes groot, dus daar zouden nog wel
wat kindprocessen bij gaan horen.
Nu maar eens kijken welke files een startende OpenOffice allemaal aanspreekt
met mijn problematische tekening:</p>

<pre><code>strace -f -e trace=desc -o mijntracefile \
          /usr/lib/openoffice/program/soffice.bin mijntekening.odg
</code></pre>

<p>Meteen na het zichtbaar worden van de tekening stopte ik OpenOffice weer.
Mijn tracefile was "maar" 15000 regels groot geworden,
hoewel 'trace=desc' het tracen beperkt tot file-descriptor gerelateerde
system calls.
Een tijdje rondbladeren gaf me de indruk dat de volgende selectie
wel eens goed zou kunnen inzoomen: zoek naar de string 'font',
maar ik hoef geen NOENT-regels (openen van een file mislukt) en geen
configuratie-bestanden:</p>

<pre><code> grep -i font mijntracefile | grep -v -e NOENT -e conf
</code></pre>

<p>En jawel: nog maar een kort lijstje over, in deze trant:</p>

<pre><code>7986  read(47, "lias our fonts to common familie"..., 8192) = 2332
7986  open("/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans.ttf", O_RDONLY) = 45
7986  open("/home/ikzelf/.openoffice.org/3/user/psprint/pspfontcache", O_RDONLY) = 47
7986  open("/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans.ttf", O_RDONLY) = 47
7986  open("/usr/share/fonts/type1/gsfonts/n021003l.pfb", O_RDONLY) = 58
7986  open("/usr/share/fonts/type1/gsfonts/n019003l.pfb", O_RDONLY) = 59
7986  open("/usr/share/fonts/type1/gsfonts/n019003l.afm", O_RDONLY) = 59
7986  open("/usr/share/fonts/type1/gsfonts/n019004l.pfb", O_RDONLY) = 62
7986  open("/usr/share/fonts/truetype/openoffice/opens___.ttf", O_RDONLY) = 62
7986  open("/usr/share/fonts/type1/gsfonts/n022004l.pfb", O_RDONLY) = 62
7986  open("/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf", O_RDONLY) = 62
</code></pre>

<p>Interessante filenaam-extensies: .ttf voor TrueType font,
.pfb voor PostScriptFont-binary, .afm voor AdobeFontMetrics</p>

<p>Vele jaren geleden heb ik eens als vingeroefening
een compleet Adobe Type 1 font gebouwd, vanaf scratch.
Ik had wel het allereenvoudigste gekozen: het
<a href="/Training/Publicaties/kixfont.pfa.tar">KIX font</a>.
Dat is het alfabet van de barcodes waarmee de adres-sorteermachines
van de NL posterijen werken.
Je ziet het vaak op brieven onder de plaatsnaam staan.
Geen ronde contouren, geen "hints" enz,
alleen rechthoekjes in verschillende maten.
Sindsdien weet ik nog de weg in .pfb en .pfa (PostScriptFont-ASCII) bestanden.
.pfb-bestanden zijn lastig hanteerbaar: een mix van tekstregels en stukken binary.
Commando's zoals vi, grep en less kunnen er niet mee omgaan,
maar het kan wel zo:</p>

<pre><code>strings  /usr/share/fonts/type1/gsfonts/n019003l.pfb | grep FontName
/FontName /NimbusSanL-Regu def
</code></pre>

<p>Bingo! Dit is een van de fonts waar het om gaat; ondanks de absurde filenaam
hebben we 'm gevonden. We kijken nu wat verder in die file met 'strings':</p>

<pre><code>%!PS-AdobeFont-1.0: NimbusSanL-Regu 1.06
%%Title: NimbusSanL-Regu
%Version: 1.06
%%CreationDate: Thu Aug  2 14:35:58 2007
%%Creator: frob
%Copyright: Copyright (URW)++,Copyright 1999 by (URW)++ Design &amp;
%Copyright:  Development; Cyrillic glyphs added by Valek Filippov (C)
%Copyright:  2001-2005
% Generated by FontForge 20070723 (http://fontforge.sf.net/)
%%EndComments
</code></pre>

<p>Dan zoeken we op een van de andere hosts naar een file met dezelfde naam:</p>

<pre><code>locate n019003l.pfb
.....
/usr/share/fonts/default/Type1/n019003l.pfb
</code></pre>

<p>Andere directory-padnaam, maar dat zal wel OK zijn.
En we kijken met 'strings' ook in die file:</p>

<pre><code>!PS-AdobeFont-1.0: NimbusSanL-Regu 1.06
%%Title: NimbusSanL-Regu
%%CreationDate: Tue Dec 31 16:51:01 2002
%%Creator: frob
%%DocumentSuppliedResources: font NimbusSanL-Regu
% Copyright (URW)++,Copyright 1999 by (URW)++ Design &amp; Development; Cyri
% Generated by PfaEdit 1.0 (http://pfaedit.sf.net/)
%%EndComments
</code></pre>

<p>Wel wel... een duidelijk verschil. Toch ging het hier om twee
actuele Linux-distributies, alleen van verschillend "merk"....</p>

<p>Om nu de rest van een lang verhaal kort te maken:
we hebben op onze verschillende Linux'en ge&iuml;nventariseerd
welke versies van deze fonts voorkwamen.
Dat bleek beperkt te blijven tot deze twee,
hoewel de directorynaam bijna overal verschillend was.
We hebben een directory met de 'CreationDate 2007' versies gepakt,
en alle files daaruit botweg gekopieerd naar de Linux'en
die de 'CreationDate 2002'-versies bevatten.
Probleem opgelost..</p>

<p>Op onze checklist bij ingebruikname van "weer een nieuwe Linux"-distributie
staat nu als vast punt erbij: font-files gelijktrekken.</p>]]></description>

</item>
<item>
<link>http://www.atcomputing.nl/blog/archives/2010/03/index.php#e2010-03-23T14_43_06.txt</link>
<guid isPermaLink="true">http://www.atcomputing.nl/blog/archives/2010/03/index.php#e2010-03-23T14_43_06.txt</guid>
<title>Nabedrukt briefpapier</title>
<dc:date>2010-03-23T14:43:06+02:00</dc:date>
<dc:creator>gait</dc:creator>
<dc:subject> Tips and Tricks</dc:subject>
<description><![CDATA[<p>Bij AT Computing zijn we overstapt van compleet voorbedrukt briefpapier
naar papier met slechts het logo voorbedrukt.  Daarvoor gebruiken we
een OpenOffice.org-sjabloon met macro's: Voor een nieuw document kun je,
uitgaande van zo'n sjabloon, kiezen uit drie briefhoofden: voorzien van
de gegevens van een van de werkmaatschappijen ('oplossers' of 'opleiders')
dan wel een algemene versie zonder bankrekeningnummers e.d.</p>

<p>Dat biedt geen soelaas voor bestaande documenten die voorheen op
voorbedrukt briefpapier werden afgedrukt. En die zijn er: er wordt
bijvoorbeeld gefactureerd vanuit een werkblad (spreadsheet). Daarvoor
werd aanvankelijk eerst naar behoefte papier met briefhoofd afgedrukt
waar in een tweede printgang de facturen op afgedrukt werden. Dat is
omslachtig, alleen al omdat je er nog steeds een voorraad op na moet
houden. Dat kan beter.</p>

<h1>Filter</h1>

<p>Om te beginnen wilde ik de vaste tekst van het briefhoofd electronisch
samenvoegen met de variabele factuurtekst. Dat was niet moeilijk,
hooguit wat omslachtig: beide componenten omzetten naar PDF, dan
deze met de PDF-toolkit (<code>pdftk</code>) samenvoegen en weer terug naar
PostScript vertalen. Dat trucje is zo gescript en daar hou je een
postscript-naar-postscript filter aan over waarbij elke pagina voorzien
is van het briefpapier.</p>

<h1>Specifieke OO.o-printer</h1>

<p>Het trucje is bruikaar bij een homebrew printer in OpenOffice.org.
Als CUPS in gebruik is bij OpenOffice.org dan kun je met spadmin,
het printerbeheerprogramma, normaal gesproken geen nieuwe printers
maken.  Maar je kunt nog wel zoals dat heet een 'PDF-conversieprogramma
aansluiten'. Hierbij heb je zelf het hele traject van OO.o naar printer
in de hand: Je stuurt dan het PostScript dat uit OO.o komt eerst door
het filter waarna je de uitvoer naar de printer stuurt. Dan werkt het
voor OO.o-gebruikers.</p>

<h1>Implementatie in CUPS</h1>

<p>Het mooiste zou zijn: de truc als optie bij het afdrukken beschikbaar
maken. Dat doe je door het traject van een printjob in CUPS aan te
passen. Een printtraject bestaat uit een pijplijn van filters die gekozen
worden op basis van het MIME-types van de invoer en het MIME-type dat
geschikt is voor een printer: Voor een overgang van het ene naar het
andere MIME-type worden filters gebruikt. Voor zo'n filter ligt vast dat
CUPS de opties als het vijfde argumenten doorgeeft. Mijn filter kijkt
of het bekende opties aantreft (bijvoorbeeld <code>atc-printedlogo</code>') en doet
dan het bijbehorende kunstje, anders filtert het ongewijzigd
(<code>/bin/cat</code>).</p>

<p>Om het eigen printfilter in het filterpad van CUPS op te nemen heb
ik een MIME-type toegevoegd aan de CUPS-configuratie door een nieuw
types-bestand te maken volgens mime.types(1). In het nieuwe bestand
<code>/etc/cups/atc-letterhead.types</code> staat</p>

<pre><code>application/atc-letterhead
</code></pre>

<p>Vervolgens heb ik in het bestaande bestand <code>/etc/cups/mime.convs</code> een regel
vervangen door twee nieuwe regels:</p>

<pre><code># invoertype            uitvoertype                     kosten filter
application/postscript  application/vnd.cups-postscript 66     pstops
</code></pre>

<p>is vervangen door</p>

<pre><code>application/postscript     application/atc-letterhead      66 atc-letterhead2ps
application/atc-letterhead application/vnd.cups-postscript 66 pstops
</code></pre>

<p>Op die kosten kom ik later nog terug. Deze constructie zorgt ervoor dat
mijn filter in het filtertraject opgenomen wordt. Het genoemde filter
wordt gevonden als <code>/etc/lib/cups/filter/atc-letterhead2ps</code> en dat filter
weet zelf waar de PDF-bestanden met briefhoofden staan.</p>

<p>De facturen worden rechtstreeks vanuit OO.o afgedrukt op de printer
'Oplossersfactuur inclusief logo op briefpapier'. Hierachter gaat het
volgende commando schuil:</p>

<pre><code>lp -dnetlp -o atc-oplossersfactuur
</code></pre>

<p>Het filter wordt altijd gebruikt. Het speelt doorgeefluik (<code>cat</code>), tenzij
er een een optie voorbij komt die herkend wordt.</p>

<h1>Printer instances</h1>

<p>Ik heb meteen iets gemaakt waardoor je alle invoer kunt voorzien van een
afgedrukt logo:</p>

<pre><code>lp -dnetlp -oatc-printedlogo
</code></pre>

<p>Dat kan mooier als je in <code>/etc/cups/lpoptions</code> (of in je eigen
<code>~/.cups/lpoptions</code>) een zogenaamde printer-instance opvoert:</p>

<pre><code>Dest netlp/printedlogo PageSize=A4 Duplex=DuplexNoTumble atc-printedlogo
</code></pre>

<p>Hierbij is <code>netlp</code> de printer en <code>printedlogo</code> de printer-instance. Let
wel: <code>lpoptions</code> wordt slechts door CUPS-clients geraadpleegd.</p>

<p>Zoals je ziet kun je zo ook andere opties vastleggen, die instellingen
krijg je kado als je de instance gebruikt:</p>

<pre><code>lp -dnetlp/printedlogo
</code></pre>

<p>Helaas zijn printer instances binnen CUPS nog niet helemaal goed
voorelkaar, want in de uitvoer van <code>lptstat -t</code> komen de
instances voorbij alsof het zelfstandige print queues zijn. OO.o
laat ze wel allemaal zien, maar ze werken allemaal gelijk omdat
OO.o niet naar lpoptions kijkt.  Daarvoor is hier in de vorm van
een PDF-conversieprogramma een workaround gepresenteerd. Acrobat
Reader geeft je wel een lijst met printernamen maar zet daar niet de
instance-aanduiding achter ...</p>

<h1>RPM</h1>

<p>Ik heb van mijn oplossing een RPM gemaakt. Maar: ik had een bestaand
bestand aangepast (<code>mime.convs</code>), dat bestand zit tevens in de CUPS-RPM,
en als je een RPM met hetzelfde bestand wit installeren heb je
een conflict. Dan kijk je nogmaals in de manpages mime.convs(1) en
mime.types(1) en lees je het advies om geen bestaande files aan te passen
doch lokale aanpassingen in lokale bestanden op te slaan. Ik had al een
eigen .types-bestand gemaakt. Maar ja, hoe zet je dan dat filtertraject
naar je hand?</p>

<p>Toen had ik een droom over de kosten die in de derde kolom van een
.convs-bestand staan: hier staan de relatieve kosten van het filter en op
basis daarvan kiest CUPS altijd de goedkoopste route.  Dat was de crux:
ik heb mijn alternatieve filterregels tegen lagere kosten in het nieuwe
bestand <code>/etc/cups/atc-letterhead.convs</code> gezet:</p>

<pre><code>application/postscript          application/atc-letterhead      22      atc-letterhead2ps
application/atc-letterhead      application/vnd.cups-postscript 22      pstops
</code></pre>

<p>Dit is de alternatieve manier om mijn filter in het filtertraject op
te nemen.</p>

<p>Bingo!</p>]]></description>

</item>
<item>
<link>http://www.atcomputing.nl/blog/archives/2010/03/index.php#e2010-03-18T15_37_01.txt</link>
<guid isPermaLink="true">http://www.atcomputing.nl/blog/archives/2010/03/index.php#e2010-03-18T15_37_01.txt</guid>
<title>BTRFS</title>
<dc:date>2010-03-18T15:37:01+02:00</dc:date>
<dc:creator>miekg</dc:creator>
<dc:subject> Infrastructuur</dc:subject>
<description><![CDATA[<p>In het kader van redundantie gebruikt <a href="http://www.atcomputing.nl">AT Computing</a> 
altijd meerdere disks in haar servers. Als je zoiets met Linux dan opzet kom je 
al gauw uit op LVM
en/of RAID. Nu heeft SUN een paar jaar geleden
<a href="http://hub.opensolaris.org/bin/view/Community+Group+zfs/whatis">ZFS</a>
gepresenteerd. ZFS is zo geavanceerd dat alle toen bestaande
file systemen meteen verouderd waren. Helaas is de licentie van ZFS
dusdanig (<a href="http://www.sun.com/cddl/">CDDL</a>) dat de ZFS code <em>niet</em> in de Linux kernel 
mag worden opgenomen.
Gelukkig is Chris Mason (van Oracle &mdash; de nieuwe eigenaar van
SUN), begonnen aan een file systeem dat nog beter dan ZFS moet worden
en wel de juiste licentie (GPLv2) heeft om in Linux te worden opgenomen.
Dit nieuwe file systeem heet: BTRFS. Je spreekt dat uit als 'the Better
File System', maar het staat voor 'B-TRee FS'.</p>

<p>BTRFS:</p>

<ul>
<li>heeft RAID <em>ingebouwd</em> (natuurlijk gestolen van
<a href="http://nl.wikipedia.org/wiki/ZFS">ZFS</a>;</li>
<li>verzorgt <em>checksumming</em> tussen verschillende devices; dus als een disk
stuk aan het gaan is, zal BTRFS dat merken;</li>
<li>heeft <em>snapshotting</em>, een snapshot is een momentopname van een
bestandssysteem; vergelijkbaar met snapshots in LVM (maar
dan <em>zonder</em> het performance verlies van LVM);</li>
<li>heeft <em>subvolumes</em>, vergelijkbaar met 
<em>logical volumes</em> in LVM;</li>
<li>bevindt zich nog in een alpha/b&eacute;ta stadium.</li>
</ul>

<p>Om eens te kijken wat BTRFS nu precies kan, hebben we even gespeeld
met de nieuwe Ubuntu 10.04 Alpha-3, zodat we in ieder geval de
nieuwste versie van BTRFS hebben.</p>

<p>Linux kan met <code>grub2</code> nog niet booten van een BTRFS volume. Om dit
euvel te verhelpen booten we nog gewoon van een RAID1 partitie met
<code>ext4</code>. Er
blijven uiteindelijk twee partities over voor BTRFS: <code>/dev/sda4</code> en
<code>/dev/sdb4</code>. Deze willen we inrichten als RAID0. BTRFS ondersteunt
ook RAID1 en RAID10 en aan RAID5 wordt gewerkt. Voor het verdere verhaal
maak de RAID versie niet uit.</p>

<h1>Ingebruikname</h1>

<p>We beginnen met het aanmaken van een BTRFS filesysteem op de twee
partities: (naar de 'WARNING!' regels moet je nog even niet kijken)</p>

<pre><code>mkfs.btrfs /dev/sda4 /dev/sdb4

WARNING! - Btrfs Btrfs v0.19 IS EXPERIMENTAL
WARNING! - see http://btrfs.wiki.kernel.org before using

adding device /dev/sdb4 id 2
fs created label (null) on /dev/sda4
nodesize 4096 leafsize 4096 sectorsize 4096 size 451.46GB
Btrfs Btrfs v0.19
</code></pre>

<p>En hier valt dus al gelijk op dat je (anders dan de meeste <code>mkfs.*</code>
tools) <em>twee</em> (of nog meer) device namen kunt opgeven. Na deze
vrij vlotte operatie kunnen we onze fonkelnieuwe ruimte gaan mounten:</p>

<pre><code># mount -t btrfs /dev/sda4 /mnt
</code></pre>

<p>We hadden hier ook <code>/dev/sdb4</code> mogen gebruiken, BTRFS <em>weet</em> dat deze
devices bij elkaar horen. </p>

<p>Nu kunnen we er files naar toe kopi&euml;ren en een beetje rondneuzen. Alles
werkt zoals je verwacht dat het zou moeten werken. Er vallen geen bitjes
op de grond.</p>

<h1>Productie</h1>

<p>Nu willen we natuurlijk het filesysteem gaan gebruiken vanaf 
het booten van de machine. Daartoe zetten we het in de <code>fstab</code></p>

<pre><code>/dev/sda4       /mnt            btrfs   defaults        0       0
</code></pre>

<p>En rebooten we. En toen was het stuk. Het blijkt dat BTRFS het filesysteem
eerst even wil <em>onderzoeken</em> en het pas daarna gaat mounten (dit zal
in latere versies worden verbeterd). Je moet daarvoor <code>btrfsctl</code>
gebruiken:</p>

<pre><code># btrfsctl -A /dev/sda4
mount /mnt
mount: wrong fs type, bad option, bad superblock on /dev/sda4,
...
# btrfsctl -A /dev/sdb4
mount /mnt          # nu is het wel succesvol
</code></pre>

<p>Allebei de partities van ons BTRFS volume moeten zo onderhanden worden
genomen. De standaard Linux boot scripts snappen dit nog niet en dus
wordt het zo wel erg moeilijk om productie te gaan draaien met BTRFS.</p>

<h1>Performance</h1>

<p>We hebben we paar hele <em>kleine</em> testjes gedaan op de (oude) test
hardware:</p>

<pre><code># dd if=/dev/zero bs=10240 count=102400 of=LARGE
102400+0 records in
102400+0 records out
1048576000 bytes (1.0 GB) copied, 6.69901 s, 157 MB/s

# dd if=/dev/zero bs=10240 count=102400 of=LARGE
102400+0 records in
102400+0 records out
1048576000 bytes (1.0 GB) copied, 6.46881 s, 162 MB/s

# time rm LARGE     # en weer weggooien
real    0m0.574s
</code></pre>

<p>Dat valt in de categorie bruikbaar. De insteek van de ontwikkelaars van
BTRFS is: <em>first make it work, then make it fast</em>. </p>

<h1>Subvolumes</h1>

<p>Zoals gezegd zijn subvolumes vergelijkbaar met <em>logical volumes</em> van
LVM. Je knipt eigenlijk een stuk ruimte uit je BTRFS volume en kunt
dat apart gaan gebruiken. De grootte van een subvolume is net zo
groot als het originele BTRFS volume waar die uit komt. Je kunt wel
een soort quota zetten op een subvolume en zo de grootte beperken; dat
hebben we hier niet getest.</p>

<p>Laten we een subvolume maken met de naam <code>home</code>, we gebruiken hiervoor
weer <code>btrfsctl</code>:</p>

<pre><code> # btrfsctl -S home /mnt
 operation complete
 Btrfs Btrfs v0.19
</code></pre>

<p>Na deze operatie is er dus een subvolume <code>home</code> gemaakt dat net zo groot is
als het originele BTRFS volume.</p>

<p>En nu gebruiken:</p>

<pre><code># mount -t btrfs -o subvol=home /dev/sda4 /mnt/home
</code></pre>

<p>Subvolumes zijn ook de basis voor snapshotting. Een snapshot
is te maken door <code>btrfsctl -s</code> (kleine 's') te gebruiken. Ook die snapshots kun
je mounten alsof het een normaal volume is.</p>

<h1>Standaard features</h1>

<p>Zaken die we ondertussen gewend zijn van andere file systemen, zoals 
bijveerbeeld <em>online resizing</em> worden gewoon ondersteund, zie voor de
details <code>btrfsctl(8)</code> en de <code>-r</code> vlag.</p>

<h1>Conclusie</h1>

<p>Op een single disk kun je best al BTRFS uitproberen. Het schijnt dat
Linus Torvalds al een half jaar geleden overgestapt is op BTRFS.
Wil je de geavanceerde features uitproberen (meerdere
disks, RAID, CRC-checking), dan kom je toch in een  b&eacute;ta-wereld van zaken die wel werken,
maar nog niet lekker in de rest van het systeem zitten ingebakken: een
<code>btrfsctl</code> voordat je kunt mounten, subvolumes die niet meteen zichtbaar
zijn, etc.</p>

<p>Ik kijk reikhalzend uit naar een 1.0 release van BTRFS, maar vooralsnog blijf
ik op ext4 + RAID<em>N</em> zitten. Of Oracle moet ZFS opnieuw
uitbrengen, maar dan met een GPLv2 licentie (maar die kans is heel klein).</p>

<p>Kijk voor meer informatie op de <a href="http://nl.wikipedia.org/wiki/Btrfs">Wikipedia</a> pagina over
BTRFS, of de weinig up-to-date wiki pagina van het
<a href="http://btrfs.wiki.kernel.org/index.php/Main_Page">project zelf</a>.  </p>]]></description>

</item>
</channel>
</rss>
