<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
 <title>Frugality Posts · HookRace Blog</title>
 <link href="https://hookrace.net/blog/feed/frugality/" rel="self"/>
 <link href="https://hookrace.net/blog/frugality/"/>
 <updated>2026-02-10T21:22:29-05:00</updated>
 <id>https://hookrace.net/blog/Frugality</id>
 <author>
   <name>Dennis Felsing</name>
   <email>dennis@felsing.org</email>
 </author>

 
 <entry>
   <title>€9 Ticket</title>
   <link href="https://hookrace.net/blog/euro9-ticket/"/>
   <updated>2022-08-20T00:00:00-04:00</updated>
   <id>https://hookrace.net/blog/euro9-ticket</id>
   <content type="html">
     &lt;p&gt;As a relief for rising energy costs and inflation Germany is offering nearly-free public transportation for the months of June, July and August. For just €9 per month you can take any kind of regional public transportation in all of Germany. That’s basically everything except the IC/ICE for long distance. This means that a monthly ticket is now as cheap as what I would normally pay for a single route. In this post I want to talk about my experiences with this &lt;a href=&quot;https://www.bahn.com/en/offers/regional/9-euro-ticket-en&quot;&gt;€9 ticket&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Update 2022-09-01: The €9 ticket ended yesterday without a similar replacement system in place. So many people are again back to paying about €100 per month for much smaller regional tickets instead (see for example this &lt;a href=&quot;https://old.reddit.com/r/de/comments/x2x01s/70_euro_f%C3%BCr_4_stationen_endlich_zur%C3%BCck_in_meinem/&quot;&gt;German Reddit thread&lt;/a&gt;. Financially it makes more sense for me personally to go back to driving. (I hope the gasoline in the car is still good after three months.) My hope is that these three months will stay in people’s minds in Germany and shape the future of public transportation to be cheaper and simpler:&lt;/p&gt;

&lt;!--more--&gt;

&lt;p&gt;&lt;a href=&quot;/public/9euro/fahrkartenautomat.webp&quot;&gt;&lt;img src=&quot;/public/9euro/fahrkartenautomat.webp&quot; alt=&quot;Fahrkartenautomat&quot; /&gt;&lt;/a&gt;
Image via &lt;a href=&quot;https://old.reddit.com/r/de/comments/26rty0/ausland_vs_deutschland/&quot;&gt;Reddit&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;train-stations&quot;&gt;Train Stations&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://assets.static-bahn.de/dam/jcr:f55563b4-0ccf-4669-bb92-459a5988691c/20220221_LN_S-Bahn_RN_867x385_Mireo.pdf&quot;&gt;&lt;img style=&quot;width: 100vw; max-width: 100vw; margin-left: -50vw; position: relative; left: 50%&quot; alt=&quot;VRN Plan&quot; src=&quot;/public/9euro/vrn.png&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the above graphic you can see the train stations in our area. Since we are situated in the &lt;a href=&quot;https://en.wikipedia.org/wiki/Rhine-Neckar&quot;&gt;Rhine-Neckar metropolitan region&lt;/a&gt; the railway network is pretty good here.&lt;/p&gt;

&lt;iframe class=&quot;osm-map&quot; allowfullscreen=&quot;&quot; src=&quot;//umap.openstreetmap.fr/en/map/train-stations_798502?scaleControl=true&amp;amp;miniMap=false&amp;amp;scrollWheelZoom=false&amp;amp;zoomControl=true&amp;amp;allowEdit=false&amp;amp;moreControl=false&amp;amp;searchControl=null&amp;amp;tilelayersControl=null&amp;amp;embedControl=null&amp;amp;datalayersControl=true&amp;amp;onLoadPanel=undefined&amp;amp;captionBar=false&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;(map made with &lt;a href=&quot;https://umap.openstreetmap.fr/en/&quot;&gt;umap&lt;/a&gt;, routes made with &lt;a href=&quot;https://www.graphhopper.com/&quot;&gt;GraphHopper&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;The closest train stop from us is St. Ilgen-Sandhausen at 2 km distance. It offers direct connections to the cities of Heidelberg (160k inhabitants), Mannheim (309k), Karlsruhe (313k), Darmstadt (159k) and Frankfurt (753k). For other directions I prefer going to a different train station since switching times are either too long or there is too much risk of missing the connecting train. Only 89% of regional trains are &lt;a href=&quot;https://www.deutschebahn.com/de/konzern/konzernprofil/zahlen_fakten/puenktlichkeitswerte-6878476&quot;&gt;on time&lt;/a&gt; in Germany, with some routes and stations having much worse rates. This is a far cry from Switzerland’s &lt;a href=&quot;https://www.admin.ch/gov/de/start/dokumentation/medienmitteilungen.msg-id-83485.html&quot;&gt;95% punctuality&lt;/a&gt; with an even stricter definition of 3 minutes versus 5 minutes delay in Germany.&lt;/p&gt;

&lt;p&gt;Another closeby train station we commonly use is HD-Weststadt/Südstadt at 6 km distance, which is good to cycle and can also be reached via Tram. Occasionally I used Heidelberg Hauptbahnhof as well as Wiesloch-Walldorf, both at slightly under 10 km distance, which are a bit larger and thus are used as stops for Regional-Express trains. These go faster than the Regionalbahn and S-Bahn trains and thus also have fewer stops.&lt;/p&gt;

&lt;h2 id=&quot;cycling-trips&quot;&gt;Cycling Trips&lt;/h2&gt;

&lt;iframe class=&quot;osm-map&quot; allowfullscreen=&quot;&quot; src=&quot;//umap.openstreetmap.fr/en/map/cycling-and-jogging-routes_798422?scaleControl=true&amp;amp;miniMap=false&amp;amp;scrollWheelZoom=false&amp;amp;zoomControl=true&amp;amp;allowEdit=false&amp;amp;moreControl=false&amp;amp;searchControl=null&amp;amp;tilelayersControl=null&amp;amp;embedControl=null&amp;amp;datalayersControl=true&amp;amp;onLoadPanel=undefined&amp;amp;captionBar=false&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;The above map contains some of the cycling trips I took by combining them with the €9 ticket. (map made with &lt;a href=&quot;https://umap.openstreetmap.fr/en/&quot;&gt;umap&lt;/a&gt;, routes made with &lt;a href=&quot;https://www.graphhopper.com/&quot;&gt;GraphHopper&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Last time I posted about cycling and work was about &lt;a href=&quot;/blog/cycling-to-work/&quot;&gt;commuting by bicycle for a year&lt;/a&gt;. I kept this up and was super happy with this way of getting to &lt;a href=&quot;https://www.sap.com/&quot;&gt;SAP&lt;/a&gt;’s headquarter in Walldorf and the included exercise. After a while I moved together with my wife and my bike commute lengthened to 10 km each direction, which was still perfectly fine. Then Covid forced me in March of 2020 to work from home I switched to regular cycling trips in the afternoon since I started working early in the morning, in order to have meetings with colleagues in Korea and China. Two 50 km cycling trips per week got me to the same level as the daily commute used to do, and I have a nice route I regularly take, which covers a lot of forest and lake.&lt;/p&gt;

&lt;p&gt;In January I switched to a new job at &lt;a href=&quot;https://www.yugabyte.com/&quot;&gt;Yugabyte&lt;/a&gt;. As the database we develop is distributed, so are the people I work with. Since most meetings fall into US times, I am now working more during the afternoon and evening. This means my cycling trips moved to the morning instead.&lt;/p&gt;

&lt;p&gt;Previously I had a job ticket for three years, but other than the €9 ticket it limits the range I can take public transportation in, so that many of my destinations would be barely outside of the covered range. When looking at a map of Germany with all the different transport associations, each with their own tickets, prices and rules, I feel reminded of &lt;a href=&quot;https://de.m.wikipedia.org/wiki/Datei:HRR_1789.png&quot;&gt;historical maps of the Holy Roman Empire&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/public/9euro/verbuende.png&quot;&gt;&lt;img src=&quot;/public/9euro/verbuende.png&quot; alt=&quot;Public Transport Associations in Germany&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Good luck figuring out what ticket to get to combine with your job ticket. Luckily the €9 ticket changed this, at least temporarily, making life simpler.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/public/9euro/oldtown.jpg&quot;&gt;&lt;img src=&quot;/public/9euro/oldtown.jpg&quot; alt=&quot;Old town at Neckar&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I used the ticket during the working days to discover new cycling routes. By cycling one direction and taking a direct train connection back, I can cover nearly twice the distance as usual with the same starting and stopping point. With this method I took many routes I had never taken before, discovering beautiful cycling paths along the Neckar river for example. Luckily the trains are not too crowded during working days, especially outside of rush hour. The cycling usually takes around three times as long as the train ride back.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/public/9euro/empty.jpg&quot;&gt;&lt;img src=&quot;/public/9euro/empty_small.jpg&quot; alt=&quot;Empty train&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The image above shows an almost empty train during a working day.&lt;/p&gt;

&lt;p&gt;Even with a single train connection things can go wrong though. One time the train in front of ours started burning and so we got delayed for an unknown time. But since I had my bicycle with me, that meant I could just cycle back the rest of the way and get some extra exercise for free. If I was really out of energy I could have waited in the train, but often busses are used as a replacement, and bicycles on busses don’t fit well.&lt;/p&gt;

&lt;p&gt;The weather was unusually dry the last months, the last few days had the first few drops of rain I remember since the beginning of June. This is of course bad for nature here, as I saw at the Rhine river, which is much lower than usual. For cycling it’s quite good though, only got wet once.&lt;/p&gt;

&lt;h2 id=&quot;jogging&quot;&gt;Jogging&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;/public/9euro/forest.jpg&quot;&gt;&lt;img src=&quot;/public/9euro/forest.jpg&quot; alt=&quot;&amp;quot;Kleiner Odenwald&amp;quot; forest&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This amount of cycling of course meant that my bicycle had to get some repairs done. While I had it at the repair shop, I tried out jogging from Heidelberg back to my home town Leimen. The route goes through the “Kleiner Odenwald” forest for nearly the entire way, and the rest are some fields. It goes through Heidelberg’s highest mountain, the Königstuhl, with a nice view of the city and surrounding area.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/public/9euro/koenigstuhl.jpg&quot;&gt;&lt;img src=&quot;/public/9euro/koenigstuhl_small.jpg&quot; alt=&quot;View from Königstuhl&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The “Ladder to Heaven” in the beginning with its 1300 steps accounts for most of the elevation and is still too tough for me to do at faster than walking speed. After I got my bicycle back I still kept doing this jogging route a few times per week.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/public/9euro/jogging.png&quot; alt=&quot;Elevation for Jogging&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;nearby-excursions&quot;&gt;Nearby Excursions&lt;/h2&gt;

&lt;p&gt;There are lots of cities and nice nature nearby. So we used public transportation for excursions on every weekend. It was noticable that the trains were crowded, and at times people couldn’t even enter the train anymore. When going to the cinema we noticed that we should really catch the last train at 22:30, since the next connection would take all night.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/public/9euro/excursion.jpg&quot;&gt;&lt;img src=&quot;/public/9euro/excursion_small.jpg&quot; alt=&quot;Cinema excursion&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One time the train back from Frankfurt was late, then got cancelled. The next train was also delayed, and now overcrowded because it had twice the passengers. Unfortunately it took me too long to realize that with more than 20 minutes delay, we should be able to take high speed ICE trains as a replacement and get the cost reimbursed by Deutsche Bahn.&lt;/p&gt;

&lt;p&gt;You still need to wear masks as a Covid prevention on public transportation in Germany. This is basically the only remaining occasion, other than at the Doctor’s office, where you have to wear masks. Even with all the travel using public transportation and basically no distancing we haven’t caught Covid as far as we are aware based on symptoms and some rapid tests.&lt;/p&gt;

&lt;h2 id=&quot;eating-out&quot;&gt;Eating Out&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;/public/9euro/food1.jpg&quot;&gt;&lt;img src=&quot;/public/9euro/food1_small.jpg&quot; alt=&quot;Food&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My wife picked out many great restaurants in nearby cities for us to try out. By car it would not make sense to just drive to another city for a dinner, but using public transportation we had no problem spending an hour on a train, where you can comfortable talk or surf the web, to reach our destinations. This probably has a really good economic effect on restaurants and cafes, many were so fully packed that we got the last seats or had to reserve beforehand.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/public/9euro/food2.jpg&quot;&gt;&lt;img src=&quot;/public/9euro/food2_small.jpg&quot; alt=&quot;More food&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;family-visits&quot;&gt;Family Visits&lt;/h2&gt;

&lt;p&gt;For trips to cities and cycling trips we mostly take direct train connections. That usually works quite well. To visit family however the destinations are smaller villages, so the connections are worse and there is no direct connection at all. So a 40 minute drive turns into a 2 hour train ride with 3 different trains instead. Coincidentally this is the same time I need for the route by bicycle.&lt;/p&gt;

&lt;p&gt;There is a relatively high chance of missing a connecting train, or of getting stuck in the middle of the route. One time a truck crashed into the train bridge, so our train couldn’t continue. Instead of waiting around for an hour or two we took the train back home and, for the only time during these three months so far, had to switch to the car to keep our timeline. At least the highway was quite empty, there seems to be some conflicting data on whether the €9 ticket actually has an effect on reducing car traffic though.&lt;/p&gt;

&lt;h2 id=&quot;getting-visits&quot;&gt;Getting Visits&lt;/h2&gt;

&lt;p&gt;While my wife and I didn’t do any long distance travel using the €9 ticket, others did. I had a co-developer of &lt;a href=&quot;https://ddnet.org/&quot;&gt;DDraceNetwork&lt;/a&gt; visit all the way from Berlin using the €9 ticket. For people who have time it has been much easier to visit others with the €9 ticket, without having to worry about cost.&lt;/p&gt;

&lt;h2 id=&quot;while-on-vacation&quot;&gt;While on Vacation&lt;/h2&gt;

&lt;p&gt;While we didn’t use the €9 ticket to go to a vacation, it was still useful during a car-based vacation. While driving to Berchtesgadener Land we saw a train line that seemed to lead to Berchtesgaden. So we stopped at the closeby train station and spontaneously took the train instead, which lead us through beautiful sceneries.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/public/9euro/berchtesgaden.jpg&quot;&gt;&lt;img src=&quot;/public/9euro/berchtesgaden.jpg&quot; alt=&quot;Berchtesgaden&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the way back from the same vacation we had a stop in Munich. Instead of driving the car all the way into the city center I checked for the closest free Park and Ride garage from the highway. After parking the car there, the train was unfortunately cancelled, so we had to take a bus and metro, but still reached the city center quickly.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/public/9euro/foreign1.jpg&quot;&gt;&lt;img src=&quot;/public/9euro/foreign1_small.jpg&quot; alt=&quot;Public transportation on Mallorca&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After having used public transportation a lot back at home, we also felt more comfortable using it on vacation in other countries, even if it costs a bit more there. So on Mallorca we explored the entire island using the extensive bus network instead of renting a car. The photo above shows the most distant bus stop we reached, at a lighthouse at the end of a long curvy road. Next week we are taking the TGV to Paris, which tops out at 320 km/h and manages the 550 km route in just 2:36, an average of 212 km/h. The image below is luckily not the TGV:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/public/9euro/foreign2.jpg&quot;&gt;&lt;img src=&quot;/public/9euro/foreign2_small.jpg&quot; alt=&quot;More public transportation on Mallorca&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Going to the airport by train worked fine, but we had to get there a few hours earlier in case was some train delay causing us to miss the connecting train. On the way back there indeed was a huge delay, with a train line being closed down for hours. After a slow trickle of information from the train operator, we had to figure out an alternative route on our own, since the DB app was still showing routes that could not be operated for the next few hours because of the train line closure.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;There are still two weeks left for the €9 ticket and I’m planning to make the best use of them. So far I’ve been taking a trip on most days, so it’s definitely been worth it and will be missed.&lt;/p&gt;

&lt;p&gt;Unfortunately the €9 ticket won’t be prolonged. The high public interest has lead to &lt;a href=&quot;https://www.thelocal.de/20220805/how-the-greens-want-to-replace-germanys-e9-ticket-deal/&quot;&gt;some plans&lt;/a&gt; for a relatively cheap follow-up ticket, but again with some regional limitations for the cheap ticket. &lt;a href=&quot;https://www.theguardian.com/money/2022/jul/15/spain-announces-free-rail-journeys-from-september-until-the-end-of-the-year&quot;&gt;Other&lt;/a&gt; &lt;a href=&quot;https://www.vdl.lu/en/getting-around/bus/free-public-transport-and-exceptions&quot;&gt;countries&lt;/a&gt; are even implementing entirely free public transport meanwhile, so there is some hope for the future.&lt;/p&gt;

   </content>
 </entry>
 
 <entry>
   <title>Linux Desktop Setup</title>
   <link href="https://hookrace.net/blog/linux-desktop-setup/"/>
   <updated>2019-01-15T00:00:00-05:00</updated>
   <id>https://hookrace.net/blog/linux-desktop-setup</id>
   <content type="html">
     &lt;p&gt;&lt;a href=&quot;https://vectorified.com/ru-linux-desktop-setup&quot;&gt;Russian Translation by Akhmad Karimov&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My software setup has been surprisingly constant over the last decade, after a few years of experimentation since I initially switched to Linux in 2006. It might be interesting to look back in another 10 years and see what changed. A quick overview of what’s running as I’m writing this post:&lt;/p&gt;

&lt;!--more--&gt;
&lt;p&gt;&lt;a href=&quot;/public/linux-desktop/htop.png&quot;&gt;&lt;img src=&quot;/public/linux-desktop/htop_small.png&quot; alt=&quot;htop overview&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;motivation&quot;&gt;Motivation&lt;/h2&gt;

&lt;p&gt;My software priorities are, in no specific order:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Programs should run on my local system so that I’m in control of them, this excludes cloud solutions.&lt;/li&gt;
  &lt;li&gt;Programs should run in the terminal, so that they can be used consistently from anywhere, including weak computers or a phone.&lt;/li&gt;
  &lt;li&gt;Keyboard focused is nearly automatic by using terminal software. I prefer to use the mouse where it makes sense only, reaching for the mouse all the time during typing feels like a waste of time. Occasionally it took me an hour to notice that the mouse wasn’t even plugged in.&lt;/li&gt;
  &lt;li&gt;Ideally use fast and efficient software, I don’t like hearing the fan and feeling the room heat up. I can also keep running older hardware for much longer, my 10 year old Thinkpad x200s is still fine for all the software I use.&lt;/li&gt;
  &lt;li&gt;Be composable. I don’t want to do every step manually, instead automate more when it makes sense. This naturally favors the shell.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;operating-systems&quot;&gt;Operating Systems&lt;/h2&gt;

&lt;p&gt;I had a hard start with Linux 12 years ago by removing Windows, armed with just the &lt;a href=&quot;https://gentoo.org/&quot;&gt;Gentoo Linux&lt;/a&gt; installation CD and a printed manual to get a functioning Linux system. It took me a few days of compiling and tinkering, but in the end I felt like I had learnt a lot.&lt;/p&gt;

&lt;p&gt;I haven’t looked back to Windows since then, but I switched to &lt;a href=&quot;https://www.archlinux.org/&quot;&gt;Arch Linux&lt;/a&gt; on my laptop after having the fan fail from the constant compilation stress. Later I also switched all my other computers and private servers to Arch Linux. As a rolling release distribution you get package upgrades all the time, but the most important breakages are nicely reported in the &lt;a href=&quot;https://www.archlinux.org/news/&quot;&gt;Arch Linux News&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One annoyance though is that Arch Linux removes the old kernel modules once you upgrade it. I usually notice that once I try plugging in a USB flash drive and the kernel fails to load the relevant module. Instead you’re supposed to reboot after each kernel upgrade. There are a few &lt;a href=&quot;https://www.reddit.com/r/archlinux/comments/4zrsc3/keep_your_system_fully_functional_after_a_kernel/&quot;&gt;hacks&lt;/a&gt; around to get around the problem, but I haven’t been bothered enough to actually use them.&lt;/p&gt;

&lt;p&gt;Similar problems happen with other programs, commonly Firefox, cron or Samba requiring a restart after an upgrade, but annoyingly not warning you that that’s the case. &lt;a href=&quot;https://www.suse.com/&quot;&gt;SUSE&lt;/a&gt;, which I use at work, nicely warns about such cases.&lt;/p&gt;

&lt;p&gt;For the &lt;a href=&quot;https://ddnet.org/&quot;&gt;DDNet&lt;/a&gt; production servers I prefer &lt;a href=&quot;https://www.debian.org/&quot;&gt;Debian&lt;/a&gt; over Arch Linux, so that I have a lower chance of breakage on each upgrade. For my firewall and router I used &lt;a href=&quot;https://www.openbsd.org/&quot;&gt;OpenBSD&lt;/a&gt; for its clean system, documentation and great &lt;a href=&quot;https://www.openbsd.org/faq/pf/&quot;&gt;pf firewall&lt;/a&gt;, but right now I don’t have a need for a separate router anymore.&lt;/p&gt;

&lt;h2 id=&quot;window-manager&quot;&gt;Window Manager&lt;/h2&gt;

&lt;p&gt;Since I started out with Gentoo I quickly noticed the huge compile time of KDE, which made it a no-go for me. I looked around for more minimal solutions, and used &lt;a href=&quot;http://openbox.org/wiki/Main_Page&quot;&gt;Openbox&lt;/a&gt; and &lt;a href=&quot;http://fluxbox.org/&quot;&gt;Fluxbox&lt;/a&gt; initially. At some point I jumped on the tiling window manager train in order to be more keyboard-focused and picked up &lt;a href=&quot;https://dwm.suckless.org/&quot;&gt;dwm&lt;/a&gt; and &lt;a href=&quot;https://awesomewm.org/&quot;&gt;awesome&lt;/a&gt; close to their initial releases.&lt;/p&gt;

&lt;p&gt;In the end I settled on &lt;a href=&quot;https://xmonad.org/&quot;&gt;xmonad&lt;/a&gt; thanks to its flexibility, extendability and being written and configured in pure &lt;a href=&quot;https://www.haskell.org/&quot;&gt;Haskell&lt;/a&gt;, a great functional programming language. One example of this is that at home I run a single 40” 4K screen, but often split it up into four virtual screens, each displaying a workspace on which my windows are automatically arranged. Of course xmonad has a &lt;a href=&quot;http://hackage.haskell.org/package/xmonad-contrib-0.15/docs/XMonad-Layout-LayoutScreens.html&quot;&gt;module&lt;/a&gt; for that.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://robm.github.io/dzen/&quot;&gt;dzen&lt;/a&gt; and &lt;a href=&quot;https://github.com/brndnmtthws/conky&quot;&gt;conky&lt;/a&gt; function as a simple enough status bar for me. My entire conky config looks like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;out_to_console yes
update_interval 1
total_run_times 0

TEXT
${downspeed eth0} ${upspeed eth0} | $cpu% ${loadavg 1} ${loadavg 2} ${loadavg 3} $mem/$memmax | ${time %F %T}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And gets piped straight into dzen2 with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;conky | dzen2 -fn &apos;-xos4-terminus-medium-r-normal-*-12-*-*-*-*-*-*-*&apos; -bg &apos;#000000&apos; -fg &apos;#ffffff&apos; -p -e &apos;&apos; -x 1000 -w 920 -xs 1 -ta r&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One important feature for me is to make the terminal emit a beep sound once a job is done. This is done simply by adding a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\a&lt;/code&gt; character to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PR_TITLEBAR&lt;/code&gt; variable in zsh, which is shown whenever a job is done. Of course I disable the actual beep sound by blacklisting the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pcspkr&lt;/code&gt; kernel module with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;echo &quot;blacklist pcspkr&quot; &amp;gt; /etc/modprobe.d/nobeep.conf&lt;/code&gt;. Instead the sound gets turned into an urgency by urxvt’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;URxvt.urgentOnBell: true&lt;/code&gt; setting. Then xmonad has an urgency hook to capture this and I can automatically focus the currently urgent window with a key combination. In dzen I get the urgent windowspaces displayed with a nice and bright &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;#ff0000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The final result in all its glory on my Laptop:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/public/linux-desktop/laptop.png&quot;&gt;&lt;img src=&quot;/public/linux-desktop/laptop_small.png&quot; alt=&quot;Laptop screenshot&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hear that &lt;a href=&quot;https://i3wm.org/&quot;&gt;i3&lt;/a&gt; has become quite popular in the last years, but it requires more manual window alignment instead of specifying automated methods to do it.&lt;/p&gt;

&lt;p&gt;I realize that there are also terminal multiplexers like &lt;a href=&quot;https://github.com/tmux/tmux/wiki&quot;&gt;tmux&lt;/a&gt;, but I still require a few graphical applications, so in the end I never used them productively.&lt;/p&gt;

&lt;h2 id=&quot;terminal-persistency&quot;&gt;Terminal Persistency&lt;/h2&gt;

&lt;p&gt;In order to keep terminals alive I use &lt;a href=&quot;http://dtach.sourceforge.net/&quot;&gt;dtach&lt;/a&gt;, which is just the detach feature of screen. In order to make every terminal on my computer detachable I wrote a &lt;a href=&quot;https://github.com/def-/tach/blob/master/tach&quot;&gt;small wrapper script&lt;/a&gt;. This means that even if I had to restart my X server I could keep all my terminals running just fine, both local and remote.&lt;/p&gt;

&lt;h2 id=&quot;shell--programming&quot;&gt;Shell &amp;amp; Programming&lt;/h2&gt;

&lt;p&gt;Instead of &lt;a href=&quot;https://www.gnu.org/software/bash/&quot;&gt;bash&lt;/a&gt; I use &lt;a href=&quot;http://www.zsh.org/&quot;&gt;zsh&lt;/a&gt; as my shell for its huge number of features.&lt;/p&gt;

&lt;p&gt;As a terminal emulator I found &lt;a href=&quot;http://software.schmorp.de/pkg/rxvt-unicode.html&quot;&gt;urxvt&lt;/a&gt; to be simple enough, support Unicode and 256 colors and has great performance. Another great feature is being able to run the urxvt client and daemon separately, so that even a large number of terminals barely takes up any memory (except for the scrollback buffer).&lt;/p&gt;

&lt;p&gt;There is only one font that looks absolutely clean and perfect to me: &lt;a href=&quot;http://terminus-font.sourceforge.net/&quot;&gt;Terminus&lt;/a&gt;. Since it’s a bitmap font everything is pixel perfect and renders extremely fast and at low CPU usage. In order to switch fonts on-demand in each terminal with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CTRL-WIN-[1-7]&lt;/code&gt; my ~/.Xdefaults contains:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;URxvt.font: -xos4-terminus-medium-r-normal-*-14-*-*-*-*-*-*-*
dzen2.font: -xos4-terminus-medium-r-normal-*-14-*-*-*-*-*-*-*

URxvt.keysym.C-M-1: command:\033]50;-xos4-terminus-medium-r-normal-*-12-*-*-*-*-*-*-*\007
URxvt.keysym.C-M-2: command:\033]50;-xos4-terminus-medium-r-normal-*-14-*-*-*-*-*-*-*\007
URxvt.keysym.C-M-3: command:\033]50;-xos4-terminus-medium-r-normal-*-18-*-*-*-*-*-*-*\007
URxvt.keysym.C-M-4: command:\033]50;-xos4-terminus-medium-r-normal-*-22-*-*-*-*-*-*-*\007
URxvt.keysym.C-M-5: command:\033]50;-xos4-terminus-medium-r-normal-*-24-*-*-*-*-*-*-*\007
URxvt.keysym.C-M-6: command:\033]50;-xos4-terminus-medium-r-normal-*-28-*-*-*-*-*-*-*\007
URxvt.keysym.C-M-7: command:\033]50;-xos4-terminus-medium-r-normal-*-32-*-*-*-*-*-*-*\007

URxvt.keysym.C-M-n: command:\033]10;#ffffff\007\033]11;#000000\007\033]12;#ffffff\007\033]706;#00ffff\007\033]707;#ffff00\007
URxvt.keysym.C-M-b: command:\033]10;#000000\007\033]11;#ffffff\007\033]12;#000000\007\033]706;#0000ff\007\033]707;#ff0000\007
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For programming and writing I use &lt;a href=&quot;https://www.vim.org/&quot;&gt;Vim&lt;/a&gt; with syntax highlighting and &lt;a href=&quot;http://ctags.sourceforge.net/&quot;&gt;ctags&lt;/a&gt; for indexing, as well as a few terminal windows with grep, sed and the other usual suspects for search and manipulation. This is probably not at the same level of comfort as an IDE, but allows me more automation.&lt;/p&gt;

&lt;p&gt;One problem with Vim is that you get so used to its key mappings that you’ll want to use them everywhere.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.python.org/&quot;&gt;Python&lt;/a&gt; and &lt;a href=&quot;https://nim-lang.org/&quot;&gt;Nim&lt;/a&gt; do well as scripting languages where the shell is not powerful enough.&lt;/p&gt;

&lt;h2 id=&quot;system-monitoring&quot;&gt;System Monitoring&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://hisham.hm/htop/&quot;&gt;htop&lt;/a&gt; works great for getting a quick overview of what the software is currently doing. &lt;a href=&quot;http://lm-sensors.org/&quot;&gt;lm_sensors&lt;/a&gt; allows monitoring the hardware temperatures, fans and voltages. &lt;a href=&quot;https://01.org/powertop/&quot;&gt;powertop&lt;/a&gt; is a great little tool by Intel to find power savings. &lt;a href=&quot;https://dev.yorhel.nl/ncdu&quot;&gt;ncdu&lt;/a&gt; lets you analyze disk usage interactively.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://nmap.org/&quot;&gt;nmap&lt;/a&gt;, iptraf-ng, &lt;a href=&quot;https://www.tcpdump.org/&quot;&gt;tcpdump&lt;/a&gt; and &lt;a href=&quot;https://www.wireshark.org/&quot;&gt;Wireshark&lt;/a&gt; are essential tools for analyzing network problems.&lt;/p&gt;

&lt;p&gt;There are of course many more great tools.&lt;/p&gt;

&lt;h2 id=&quot;mails--synchronization&quot;&gt;Mails &amp;amp; Synchronization&lt;/h2&gt;

&lt;p&gt;On my home server I have a &lt;a href=&quot;http://www.fetchmail.info/&quot;&gt;fetchmail&lt;/a&gt; daemon running for each email acccount that I have. Fetchmail just retrieves the incoming emails and invokes &lt;a href=&quot;http://www.procmail.org/&quot;&gt;procmail&lt;/a&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/sh&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;i &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; /home/deen/.fetchmail/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do
  &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;FETCHMAILHOME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$i&lt;/span&gt; /usr/bin/fetchmail &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;procmail -d %T&apos;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; 60
&lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The configuration is as simple as it could be and waits for the server to inform us of fresh emails:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;poll imap.1und1.de protocol imap timeout 120 user &quot;dennis@felsing.org&quot; password &quot;XXX&quot; folders INBOX keep ssl idle
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;My &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.procmailrc&lt;/code&gt; config contains a few rules to backup all mails and sort them into the correct directories, for example based on the mailing list id or from field in the mail header:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;MAILDIR=/home/deen/shared/Maildir
LOGFILE=$HOME/.procmaillog
LOGABSTRACT=no
VERBOSE=off
FORMAIL=/usr/bin/formail
NL=&quot;
&quot;

:0wc
* ! ? test -d /media/mailarchive/`date +%Y`
| mkdir -p /media/mailarchive/`date +%Y`

# Make backups of all mail received in format YYYY/YYYY-MM
:0c
/media/mailarchive/`date +%Y`/`date +%Y-%m`

:0
* ^From: .*(.*@.*.kit.edu|.*@.*.uka.de|.*@.*.uni-karlsruhe.de)
$MAILDIR/.uni/

:0
* ^list-Id:.*lists.kit.edu
$MAILDIR/.uni-ml/

[...]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To send emails I use &lt;a href=&quot;https://marlam.de/msmtp/&quot;&gt;msmtp&lt;/a&gt;, which is also great to configure:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;account default
host smtp.1und1.de
tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
auth on
from dennis@felsing.org
user dennis@felsing.org
password XXX

[...]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But so far the emails are still on the server. My documents are all stored in a directory that I synchronize between all computers using &lt;a href=&quot;https://www.cis.upenn.edu/~bcpierce/unison/&quot;&gt;Unison&lt;/a&gt;. Think of Unison as a bidirectional interactive &lt;a href=&quot;https://rsync.samba.org/&quot;&gt;rsync&lt;/a&gt;. My emails are part of this documents directory and thus they end up on my desktop computers.&lt;/p&gt;

&lt;p&gt;This also means that while the emails reach my server immediately, I only fetch them on deman instead of getting instant notifications when an email comes in.&lt;/p&gt;

&lt;p&gt;From there I read the mails with &lt;a href=&quot;http://www.mutt.org/&quot;&gt;mutt&lt;/a&gt;, using the sidebar plugin to display my mail directories. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/mailcap&lt;/code&gt; file is essential to display non-plaintext mails containing HTML, Word or PDF:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;text/html;w3m -I %{charset} -T text/html; copiousoutput
application/msword; antiword %s; copiousoutput
application/pdf; pdftotext -layout /dev/stdin -; copiousoutput
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;news--communication&quot;&gt;News &amp;amp; Communication&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://newsboat.org/&quot;&gt;Newsboat&lt;/a&gt; is a nice little RSS/Atom feed reader in the terminal. I have it running on the server in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tach&lt;/code&gt; session with about 150 feeds. Filtering feeds locally is also possible, for example:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ignore-article &quot;https://forum.ddnet.org/feed.php&quot; &quot;title =~ \&quot;Map Testing •\&quot; or title =~ \&quot;Old maps •\&quot; or title =~ \&quot;Map Bugs •\&quot; or title =~ \&quot;Archive •\&quot; or title =~ \&quot;Waiting for mapper •\&quot; or title =~ \&quot;Other mods •\&quot; or title =~ \&quot;Fixes •\&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I use &lt;a href=&quot;https://irssi.org/&quot;&gt;Irssi&lt;/a&gt; the same way for communication via IRC.&lt;/p&gt;

&lt;h2 id=&quot;calendar&quot;&gt;Calendar&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://www.roaringpenguin.com/products/remind&quot;&gt;remind&lt;/a&gt; is a calendar that can be used from the command line. Setting new reminders is done by editing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rem&lt;/code&gt; files:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# One time events
REM 2019-01-20 +90 Flight to China %b

# Recurring Holidays
REM 1 May +90 Holiday &quot;Tag der Arbeit&quot; %b
REM [trigger(easterdate(year(today()))-2)] +90 Holiday &quot;Karfreitag&quot; %b

# Time Change
REM Nov Sunday 1 --7 +90 Time Change (03:00 -&amp;gt; 02:00) %b
REM Apr Sunday 1 --7 +90 Time Change (02:00 -&amp;gt; 03:00) %b

# Birthdays
FSET birthday(x) &quot;&apos;s &quot; + ord(year(trigdate())-x) + &quot; birthday is %b&quot;
REM 16 Apr +90 MSG Andreas[birthday(1994)]

# Sun
SET $LatDeg 49
SET $LatMin 19
SET $LatSec 49
SET $LongDeg -8
SET $LongMin -40
SET $LongSec -24

MSG Sun from [sunrise(trigdate())] to [sunset(trigdate())]
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Unfortunately there is no Chinese Lunar calendar function in remind yet, so Chinese holidays can’t be calculated easily.&lt;/p&gt;

&lt;p&gt;I use two aliases for remind:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;rem -m -b1 -q -g
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;to see a list of the next events in chronological order and&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;rem -m -b1 -q -cuc12 -w$(($(tput cols)+1)) | sed -e &quot;s/\f//g&quot; | less
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;to show a calendar fitting just the width of my terminal:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/public/linux-desktop/remcal.png&quot; alt=&quot;remcal&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;dictionary&quot;&gt;Dictionary&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/tsdh/rdictcc&quot;&gt;rdictcc&lt;/a&gt; is a little known dictionary tool that uses the excellent dictionary files from &lt;a href=&quot;https://www.dict.cc/&quot;&gt;dict.cc&lt;/a&gt; and turns them into a local database:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ rdictcc rasch
====================[ A =&amp;gt; B ]====================
rasch:
    - apace
    - brisk [speedy]
    - cursory
    - in a timely manner
    - quick
    - quickly
    - rapid
    - rapidly
    - sharpish  [Br.]  [coll.]
    - speedily
    - speedy
    - swift
    - swiftly
rasch [gehen]:
    - smartly [quickly]
Rasch {n} [Zittergras-Segge]:
    - Alpine grass [Carex brizoides]
    - quaking grass sedge [Carex brizoides]
Rasch {m} [regional] [Putzrasch]:
    - scouring pad
====================[ B =&amp;gt; A ]====================
Rasch model:
    - Rasch-Modell {n}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;writing-and-reading&quot;&gt;Writing and Reading&lt;/h2&gt;

&lt;p&gt;I have a simple todo file containing my tasks, that is basically always sitting open in a Vim session. For work I also use the todo file as a “done” file so that I can later check what tasks I finished on each day.&lt;/p&gt;

&lt;p&gt;For writing documents, letters and presentations I use &lt;a href=&quot;https://www.latex-project.org/&quot;&gt;LaTeX&lt;/a&gt; for its superior typesetting. A simple letter in German format can be set like this for example:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-latex&quot; data-lang=&quot;latex&quot;&gt;&lt;span class=&quot;k&quot;&gt;\documentclass&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;[paper = a4, fromalign = right]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;scrlttr2&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;\usepackage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;german&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;\usepackage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;eurosym&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;\usepackage&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;[utf8]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;inputenc&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;\setlength&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;\parskip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}{&lt;/span&gt;6pt&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;\setlength&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;\parindent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}{&lt;/span&gt;0pt&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;\setkomavar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;fromname&lt;span class=&quot;p&quot;&gt;}{&lt;/span&gt;Dennis Felsing&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;\setkomavar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;fromaddress&lt;span class=&quot;p&quot;&gt;}{&lt;/span&gt;Meine Str. 1&lt;span class=&quot;k&quot;&gt;\\&lt;/span&gt;69181 Leimen&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;\setkomavar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;subject&lt;span class=&quot;p&quot;&gt;}{&lt;/span&gt;Titel&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;\setkomavar*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;enclseparator&lt;span class=&quot;p&quot;&gt;}{&lt;/span&gt;Anlagen&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;\makeatletter&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;\@&lt;/span&gt;setplength&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;refvpos&lt;span class=&quot;p&quot;&gt;}{&lt;/span&gt;89mm&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;\makeatother&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;\begin{document}&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;\begin{letter}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;Herr Soundso&lt;span class=&quot;k&quot;&gt;\\&lt;/span&gt;Deine Str. 2&lt;span class=&quot;k&quot;&gt;\\&lt;/span&gt;69121 Heidelberg&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;\opening&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;Sehr geehrter Herr Soundso,&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

Sie haben bei mir seit dem Bla Bla Bla.

Ich fordere Sie hiermit zu Bla Bla Bla auf.

&lt;span class=&quot;k&quot;&gt;\closing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;Mit freundlichen Grüßen&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;\end{letter}&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;\end{document}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Further example documents and presentations can be found over at &lt;a href=&quot;https://dennis.felsing.org/research/&quot;&gt;my private site&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To read PDFs &lt;a href=&quot;https://pwmt.org/projects/zathura/&quot;&gt;Zathura&lt;/a&gt; is fast, has Vim-like controls and even supports two different PDF backends: Poppler and MuPDF. &lt;a href=&quot;https://wiki.gnome.org/Apps/Evince&quot;&gt;Evince&lt;/a&gt; on the other hand is more full-featured for the cases where I encounter documents that Zathura doesn’t like.&lt;/p&gt;

&lt;h2 id=&quot;graphical-editing&quot;&gt;Graphical Editing&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://www.gimp.org/&quot;&gt;GIMP&lt;/a&gt; and &lt;a href=&quot;https://inkscape.org/&quot;&gt;Inkscape&lt;/a&gt; are easy choices for photo editing and interactive vector graphics respectively.&lt;/p&gt;

&lt;p&gt;In some cases &lt;a href=&quot;https://imagemagick.org/Usage/&quot;&gt;Imagemagick&lt;/a&gt; is good enough though and can be used straight from the command line and thus automated to edit images. Similarly &lt;a href=&quot;https://www.graphviz.org/&quot;&gt;Graphviz&lt;/a&gt; and &lt;a href=&quot;https://sourceforge.net/projects/pgf/&quot;&gt;TikZ&lt;/a&gt; can be used to draw graphs and other diagrams.&lt;/p&gt;

&lt;h2 id=&quot;web-browsing&quot;&gt;Web Browsing&lt;/h2&gt;

&lt;p&gt;As a web browser I’ve always used &lt;a href=&quot;https://www.mozilla.org/en-US/firefox/new/&quot;&gt;Firefox&lt;/a&gt; for its extensibility and low resource usage compared to Chrome.&lt;/p&gt;

&lt;p&gt;Unfortunately the &lt;a href=&quot;https://github.com/5digits/dactyl&quot;&gt;Pentadactyl&lt;/a&gt; extension development stopped after Firefox switched to Chrome-style extensions entirely, so I don’t have satisfying Vim-like controls in my browser anymore.&lt;/p&gt;

&lt;h2 id=&quot;media-players&quot;&gt;Media Players&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://mpv.io/&quot;&gt;mpv&lt;/a&gt; with hardware decoding allows watching videos at 5% CPU load using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vo=gpu&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hwdec=vaapi&lt;/code&gt; config settings. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;audio-channels=2&lt;/code&gt; in mpv seems to give me clearer downmixing to my stereo speakers / headphones than what PulseAudio does by default. A great little feature is exiting with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift-Q&lt;/code&gt; instead of just &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Q&lt;/code&gt; to save the playback location. When watching with someone with another native tongue you can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--secondary-sid=&lt;/code&gt; to show two subtitles at once, the primary at the bottom, the secondary at the top of the screen&lt;/p&gt;

&lt;p&gt;My wirelss mouse can easily be made into a remote control with mpv with a small &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.config/mpv/input.conf&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;MOUSE_BTN5 run &quot;mixer&quot; &quot;pcm&quot; &quot;-2&quot;
MOUSE_BTN6 run &quot;mixer&quot; &quot;pcm&quot; &quot;+2&quot;
MOUSE_BTN1 cycle sub-visibility
MOUSE_BTN7 add chapter -1
MOUSE_BTN8 add chapter 1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://rg3.github.io/youtube-dl/&quot;&gt;youtube-dl&lt;/a&gt; works great for watching videos hosted online, best quality can be achieved with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-f bestvideo+bestaudio/best --all-subs --embed-subs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As a music player &lt;a href=&quot;http://moc.daper.net/&quot;&gt;MOC&lt;/a&gt; hasn’t been actively developed for a while, but it’s still a simple player that plays every format conceivable, including the strangest Chiptune formats. In the AUR there is a &lt;a href=&quot;https://aur.archlinux.org/packages/moc-pulse/&quot;&gt;patch&lt;/a&gt; adding PulseAudio support as well. Even with the CPU clocked down to 800 MHz MOC barely uses 1-2% of a single CPU core.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/public/linux-desktop/moc.png&quot; alt=&quot;moc&quot; /&gt;&lt;/p&gt;

&lt;p&gt;My music collection sits on my home server so that I can access it from anywhere. It is mounted using &lt;a href=&quot;https://github.com/libfuse/sshfs&quot;&gt;SSHFS&lt;/a&gt; and automount in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/fstab/&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;root@server:/media/media  /mnt/media  fuse.sshfs noauto,x-systemd.automount,idmap=user,IdentityFile=/root/.ssh/id_rsa,allow_other,reconnect 0 0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;cross-platform-building&quot;&gt;Cross-Platform Building&lt;/h2&gt;

&lt;p&gt;Linux is great to build packages for any major operating system except Linux itself! In the beginning I used &lt;a href=&quot;https://www.qemu.org/&quot;&gt;QEMU&lt;/a&gt; to with an old Debian, Windows and Mac OS X VM to build for these platforms.&lt;/p&gt;

&lt;p&gt;Nowadays I switched to using chroot for the old Debian distribution (for maximum Linux compatibility), &lt;a href=&quot;http://www.mingw.org/&quot;&gt;MinGW&lt;/a&gt; to cross-compile for Windows and &lt;a href=&quot;https://github.com/tpoechtrager/osxcross&quot;&gt;OSXCross&lt;/a&gt; to cross-compile for Mac OS X.&lt;/p&gt;

&lt;p&gt;The script used to &lt;a href=&quot;https://github.com/ddnet/ddnet-scripts/blob/master/ddnet-release.sh&quot;&gt;build DDNet&lt;/a&gt; as well as the &lt;a href=&quot;https://github.com/ddnet/ddnet-scripts/blob/master/ddnet-lib-update.sh&quot;&gt;instructions for updating library builds&lt;/a&gt; are based on this.&lt;/p&gt;

&lt;h2 id=&quot;backups&quot;&gt;Backups&lt;/h2&gt;

&lt;p&gt;As usual, we nearly forgot about backups. Even if this is the last chapter, it should not be an afterthought.&lt;/p&gt;

&lt;p&gt;I wrote &lt;a href=&quot;https://github.com/def-/rrb/blob/master/rrb&quot;&gt;rrb&lt;/a&gt; (reverse rsync backup) 10 years ago to wrap rsync so that I only need to give the backup server root SSH rights to the computers that it is backing up. Surprisingly rrb needed 0 changes in the last 10 years, even though I kept using it the entire time.&lt;/p&gt;

&lt;p&gt;The backups are stored straight on the filesystem. Incremental backups are implemented using hard links (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--link-dest&lt;/code&gt;). A simple &lt;a href=&quot;https://github.com/def-/rrb/blob/master/config.example&quot;&gt;config&lt;/a&gt; defines how long backups are kept, which defaults to:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;KEEP_RULES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=(&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
   7  7 &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# One backup a day for the last 7 days&lt;/span&gt;
  31  8 &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# 8 more backups for the last month&lt;/span&gt;
 365 11 &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# 11 more backups for the last year&lt;/span&gt;
1825  4 &lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# 4 more backups for the last 5 years&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Since some of my computers don’t have a static IP / DNS entry and I still want to back them up using rrb I use a reverse SSH tunnel (as a systemd service) for them:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-cfg&quot; data-lang=&quot;cfg&quot;&gt;[Unit]
Description=Reverse SSH Tunnel
After=network.target

[Service]
ExecStart=/usr/bin/ssh -N -R 27276:localhost:22 -o &quot;ExitOnForwardFailure yes&quot; server
KillMode=process
Restart=always

[Install]
WantedBy=multi-user.target&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now the server can reach the client through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ssh -p 27276 localhost&lt;/code&gt; while the tunnel is running to perform the backup, or in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.ssh/config&lt;/code&gt; format:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Host cr-remote
  HostName localhost
  Port 27276
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;While talking about SSH hacks, sometimes a server is not easily reachable thanks to some bad routing. In that case you can route the SSH connection through another server to get better routing, in this case going through the USA to reach my Chinese server which had not been reliably reachable from Germany for a few weeks:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Host chn.ddnet.org
  ProxyCommand ssh -q usa.ddnet.org nc -q0 chn.ddnet.org 22
  Port 22
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;final-remarks&quot;&gt;Final Remarks&lt;/h2&gt;

&lt;p&gt;Thanks for reading my random collection of tools. I probably forgot many programs that I use so naturally every day that I don’t even think about them anymore. Let’s see how stable my software setup stays in the next years. If you have any questions, feel free to get in touch with me at &lt;a href=&quot;mailto:dennis@felsing.org&quot;&gt;dennis@felsing.org&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Comments on &lt;a href=&quot;https://news.ycombinator.com/item?id=19253072&quot;&gt;Hacker News&lt;/a&gt;.&lt;/p&gt;

   </content>
 </entry>
 
 <entry>
   <title>One year of cycling to work</title>
   <link href="https://hookrace.net/blog/cycling-to-work/"/>
   <updated>2018-02-20T00:00:00-05:00</updated>
   <id>https://hookrace.net/blog/cycling-to-work</id>
   <content type="html">
     &lt;p&gt;Exactly on this day, one year ago, I came back from a one month long trip to
Taiwan, went straight to work from the airport and immediately moved into a new
apartment after work. Since then I have cycled to work nearly every day.&lt;/p&gt;

&lt;!--more--&gt;

&lt;iframe allowfullscreen=&quot;&quot; class=&quot;osm-map&quot; src=&quot;https://umap.openstreetmap.fr/en/map/one-year-of-cycling-to-work_199573?scaleControl=true&amp;amp;miniMap=false&amp;amp;scrollWheelZoom=true&amp;amp;zoomControl=false&amp;amp;allowEdit=false&amp;amp;moreControl=false&amp;amp;searchControl=false&amp;amp;tilelayersControl=false&amp;amp;embedControl=false&amp;amp;datalayersControl=false&amp;amp;onLoadPanel=undefined&amp;amp;captionBar=false&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;&lt;a href=&quot;https://umap.openstreetmap.fr/en/map/one-year-of-cycling-to-work_199573#13/49.3166/8.6605&quot;&gt;Fullscreen&lt;/a&gt; (map made with &lt;a href=&quot;https://umap.openstreetmap.fr/en/&quot;&gt;umap&lt;/a&gt;, routes made with &lt;a href=&quot;https://www.graphhopper.com/&quot;&gt;GraphHopper&lt;/a&gt;)&lt;/p&gt;

&lt;h2 id=&quot;alternatives-and-motivation&quot;&gt;Alternatives and motivation&lt;/h2&gt;

&lt;p&gt;Previously I lived in Heidelberg, 15 km from work and usually took the train,
only cycling to work about once a week. But my new apartment was significantly
closer to work, only 6 km, which takes just 15-20 minutes to cycle, so it
became much easier to use the bicycle to get to work every day.&lt;/p&gt;

&lt;p&gt;Taking public transportation instead is possible, and takes about 35-40
minutes. I only do that when I want to go to the city straight after work
instead of going home first. For me personally public transportation is more
useful in combination with the bicycle on longer trips.&lt;/p&gt;

&lt;p&gt;Sometimes I take public transportation to work and just walk back in the
evening, which can be great for listening to audiobooks.&lt;/p&gt;

&lt;p&gt;For about a month I had borrowed a car and sometimes used it to go to work.
Without traffic the driving would take 12 minutes, but with traffic it’s
usually 15-25 minutes.  Additionally I have to get a parking spot and walk
further to the office, so using a car doesn’t save time compared to cycling.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/public/cycling/IMG_20170414_091754.jpg&quot;&gt;&lt;img src=&quot;/public/cycling/IMG_20170414_091754_small.jpg&quot; alt=&quot;Public transportation&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;advantages&quot;&gt;Advantages&lt;/h2&gt;

&lt;p&gt;I remember feeling tired when getting to work early in the morning using public
transportation. After all you just keep sitting and standing, never really jump
starting the body. While cycling to work I certainly wake up and feel more
energetic and focussed when I arrive.&lt;/p&gt;

&lt;p&gt;Having a clear separation of work and private life is important for me, both by
using different spaces for each as well as having a strict time separation. So
after I get out of work and get onto my bicycle I will still be having
work-related thoughts circling in my head. But while cycling I might take a
quick stop to write down any insights that I have or tasks that I forgot to
finish. Then I can finally shut down and clear my brain from work by enjoying
the ride, the views and the physical exercise.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/public/cycling/IMG_20170409_184910-PANO.jpg&quot;&gt;&lt;img src=&quot;/public/cycling/IMG_20170409_184910-PANO_small.jpg&quot; alt=&quot;Stream&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I find the simplicity of cycling for locomotion beautiful. I never have to be
stuck in a traffic jam, feeling stressed by moving at a snail’s pace. Even if
cycling is slower the feeling of making progress is stronger. I never have to
be waiting for a late train, a bus that left too early or be stuck inside of a
train while there are problems with the power line. In the worst case I can
pick up my bicycle and walk around some obstacle or fix a flat tire in a few
minutes. Being outside in nature, at least for half an hour every day, feels
good.&lt;/p&gt;

&lt;p&gt;Combined with the gym at work I feel like I’m getting enough sports to be
reasonably fit without having to spend much time on it. I regularly get home
from work at 17:00 with all work and sports done and can enjoy the rest of the
day without any worry.&lt;/p&gt;

&lt;p&gt;When I’m using the bicycle to get to work already, I feel like the barrier to
combining this with larger trips is lowered. I occasionally do 40-50 km cycling
tours before or after work, depending on the weather and my mood.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/public/cycling/IMG_20170614_172048.jpg&quot;&gt;&lt;img src=&quot;/public/cycling/IMG_20170614_172048_small.jpg&quot; alt=&quot;Longer trip&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;accommodation&quot;&gt;Accommodation&lt;/h2&gt;

&lt;p&gt;At home my bicycle is locked into a room in the basement, making it reasonably
secure and still easily accessible. At work we have roofed-over parking spaces
for the bicycles with robust metal bars for locking. Some of the cycle racks
even have two floors with an easy mechanism for getting your bicycle down from
there. When I don’t need my lock I will just leave it there after work for the
next day.&lt;/p&gt;

&lt;p&gt;Multiple buildings at work have special showers for cyclists. But to be honest
I don’t use them after my usual route to work, for three reasons:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;It is cooler in the morning.&lt;/li&gt;
  &lt;li&gt;The distance is short enough.&lt;/li&gt;
  &lt;li&gt;I cycle slower when going to work in the morning than when going back.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Especially in the summer it is nice to do a two hour cycling trip at 5 am and
go straight to work afterwards. Then the showers are a godsend of course.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/public/cycling/IMG_20170404_073617.jpg&quot;&gt;&lt;img src=&quot;/public/cycling/IMG_20170404_073617_small.jpg&quot; alt=&quot;Morning fog&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-journey-is-the-reward&quot;&gt;The journey is the reward&lt;/h2&gt;

&lt;p&gt;Close to my home I have to cross a red light, which is also where I had the
only accident in this year. A car driver ignored my right of way and so I had
to break hard and ended up sitting on their hood. But luckily that didn’t
result in any injuries or damages. (Edit: I have never had an accident
involving a car before, so this is probably an outlier.)&lt;/p&gt;

&lt;p&gt;After exiting the industrial area I am riding exclusively on small field roads.
Especially in the morning this makes for nice sunrises with fog on parts of the
fields. Along the way there is also a fishing lake.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/public/cycling/IMG_20171018_170639.jpg&quot;&gt;&lt;img src=&quot;/public/cycling/IMG_20171018_170639_small.jpg&quot; alt=&quot;Fishing lake&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The way tunnels below train tracks, runs over a stream, tunnels below a large
street and finally bridges over another large street. This means there are no
large intersections where you have to wait or be cautious. In consequence the
ride is quite relaxing.&lt;/p&gt;

&lt;p&gt;The only potential stop along the way is a small airfield where planes are
occasionally landing and starting. But that’s fun to watch, so I don’t mind
waiting for a minute or two.&lt;/p&gt;

&lt;div class=&quot;startvideo&quot;&gt;&lt;div class=&quot;video-container&quot;&gt;
  &lt;div class=&quot;ytplayer&quot; data-id=&quot;lx6VtG5S-AI&quot;&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;
&lt;script src=&quot;/public/youtube.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;

&lt;h2 id=&quot;numbers&quot;&gt;Numbers&lt;/h2&gt;

&lt;p&gt;There are about 250 work days in a year in Baden-Württemberg, excluding
weekends and public holidays. Of those I was missing for 34 days for vacations,
being sick and doing home office. On 11 days I used public transportation, on 5
days a car. That leaves 200 days on which I cycled to work.&lt;/p&gt;

&lt;p&gt;The distance to work is 6 km each way, so in total I cycled 2400 km just to get
to work and back. I need 20 minutes to work and 15 minutes back, so 117 hours
were spent cycling.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/public/cycling/T-Randonneur_Apex_2x10.jpg&quot;&gt;&lt;img src=&quot;/public/cycling/T-Randonneur_Apex_2x10_small.jpg&quot; alt=&quot;T-Randonneur (mine is in green though)&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I had two flat tires, one accident and needed one major part replacement in the
entire year. But to be fair I also cycled in my free time and it was the first
large replacement my bicycle needed since I started using it 15.000 km ago.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/public/cycling/IMG_20170928_183309.jpg&quot;&gt;&lt;img src=&quot;/public/cycling/IMG_20170928_183309_small.jpg&quot; alt=&quot;New parts&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;animal-nature&quot;&gt;Animal nature&lt;/h2&gt;

&lt;p&gt;On one day in summer I counted the wild animals that I saw on my short trip to
work, which included 4 rabbits, 1 deer with fawn, 1 white stork with young in
the nest as well as a group of 21 white storks in the fields. A few weeks ago I
was happy to see an elusive black stork flying towards the closeby forest for
the first time. On another occurence there was a Heron successfully catching a
snake.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/public/cycling/IMG_20170602_162849.jpg&quot;&gt;&lt;img src=&quot;/public/cycling/IMG_20170602_162849_small.jpg&quot; alt=&quot;Storks&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Apart from wild animals there are of course many people walking their dogs, and
the pet home on the way features lots of dogs behind the fence as well as a few
cats walking around outside.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/public/cycling/IMG_20171016_162141.jpg&quot;&gt;&lt;img src=&quot;/public/cycling/IMG_20171016_162141_small.jpg&quot; alt=&quot;Lazy cats&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;winter-is-coming&quot;&gt;Winter is coming&lt;/h2&gt;

&lt;p&gt;Appropriate clothing is important since cycling exposes you to wind and
precipitation.&lt;/p&gt;

&lt;p&gt;In summer a t-shirt and short pants are usually fine. For colder times
dual-function pants for office and cycling are great, otherwise jeans also
work.&lt;/p&gt;

&lt;p&gt;During rains I certainly don’t want to wear jeans. Then I also need to bring
another set of clothes to switch to once I’m in the office. The most important
measure for rainy days is to have good timing, since I have flexible work times
and the rains are usually not equally strong for hours. Weather forecasts can
be remarkably inaccurate though.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/public/cycling/IMG_20160118_144437.jpg&quot;&gt;&lt;img src=&quot;/public/cycling/IMG_20160118_144437_small.jpg&quot; alt=&quot;Cycling in snow (photo from 2016)&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I use wind-proof jackets and shoes as well as gloves and a balaclava in winter.
Snow and ice are rather rare and not so strong here, so they are not a big
issue. The above photo was taken two years ago and that represents about the
worst winter conditions I ever had to cycle in.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;/public/cycling/IMG_20170526_183012.jpg&quot;&gt;&lt;img src=&quot;/public/cycling/IMG_20170526_183012_small.jpg&quot; alt=&quot;The road ahead&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’ll keep cycling to work in the foreseeable future. I don’t think this post is
of much use to anyone, but I had fun reflecting my last year of cycling to
work. Thanks for reading. Comments on &lt;a href=&quot;https://news.ycombinator.com/item?id=16420271&quot;&gt;Hacker News&lt;/a&gt;.&lt;/p&gt;

   </content>
 </entry>
 
 <entry>
   <title>Broken Hardware, Fixes and Hacks over 8 Years</title>
   <link href="https://hookrace.net/blog/broken-hardware-fixes-hacks-8-years/"/>
   <updated>2016-06-21T00:00:00-04:00</updated>
   <id>https://hookrace.net/blog/broken-hardware-fixes-hacks-8-years</id>
   <content type="html">
     &lt;p&gt;After reading the feedback of my &lt;a href=&quot;/blog/ddnet-evolution-architecture-technology/&quot;&gt;recent article about running
DDNet&lt;/a&gt;, I noticed that people
found it interesting how I’m trying to minimize money and resources. I also
noticed that I had been doing something similar with my personal computing
hardware setup for an even longer time.&lt;/p&gt;

&lt;p&gt;I’ve mostly been using the same hardware for personal computation purposes over
the last 8 years. In this article I want to talk about some of the hardware
I’ve been using, how it broke and how I fixed the problems or worked around
them. My goal was to be frugal about hardware, to keep using the same hardware
for a long time and repair it when possible instead of simply buying new
hardware.&lt;/p&gt;

&lt;!--more--&gt;
&lt;p&gt;The reason for this post is that I’m moving and abandoning my old hardware
setup. There may be some interesting tales in here. Depending on how you value
your time it is probably cheaper to simply buy new stuff instead of repairing
old ones, but I consider it a fun activity and a much more rewarding
experience.&lt;/p&gt;

&lt;p&gt;Whether you call it planned obsolescence or just cheap manufacturing, it’s a
fact that electronic hardware tends to break rather quickly. Most reviews don’t
take any note of this and so it is difficult to find out which hardware to buy
if you value durability. The best I can do is tell you what you should and
should not have bought 8 years ago.&lt;/p&gt;

&lt;p&gt;For each of these topics there are probably much more detailed reports and
guides online, so I will keep it short and just give an overview. If you’re
interested in fixing your own hardware, a search engine of your choice is your
friend.&lt;/p&gt;

&lt;h2 id=&quot;tools-used&quot;&gt;Tools used&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/public/hardware/weller-wecp-20.jpg&quot; alt=&quot;Weller WECP-20&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The tools I use are absolutely simple: Mostly I just used a Weller WECP-20 for
soldering, regular screwdrivers and some other, older hardware to steal
replacement parts from. Nothing fancy and I don’t claim to do anything special
with it.&lt;/p&gt;

&lt;h2 id=&quot;liquid-crystal-displays&quot;&gt;Liquid-Crystal Displays&lt;/h2&gt;

&lt;p&gt;Let’s start with what in my experience was the most common problem in consumer
electronics, resulting in their failure: broken capacitors&lt;/p&gt;

&lt;p&gt;Or at least it was a major problem for me, probably because much of my failing
hardware was produced around the time of the &lt;a href=&quot;https://en.wikipedia.org/wiki/Capacitor_Plague&quot;&gt;capacitor
plague&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img style=&quot;max-width: 58%; display: inline; padding: 0;&quot; src=&quot;/public/hardware/bad-caps.jpg&quot; alt=&quot;Various bad capacitors&quot; /&gt;&lt;img style=&quot;max-width: 40%; display: inline; padding: 0 0 0 1mm;&quot; src=&quot;/public/hardware/bad-caps-2.jpg&quot; alt=&quot;Another bulging capacitor&quot; /&gt;&lt;/p&gt;

&lt;p&gt;On the right picture  you can see difference between a good capacitor (left)
and a bulging capacitor (right).&lt;/p&gt;

&lt;p&gt;I used a BENQ FP91GP display for the entire time. At some point I also added a
used Samsung SyncMaster P2250.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;halfimg&quot; src=&quot;/public/hardware/fp91gp.jpg&quot; alt=&quot;BENQ FP91GP&quot; /&gt;&lt;img class=&quot;halfimg&quot; src=&quot;/public/hardware/samsung-syncmaster-p2250.jpg&quot; alt=&quot;Samsung SyncMaster P2250&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Both of them failed at some point, stopping to display anything and making
high-pitched sounds instead. So of course the first thing I did was open them
up. In both of them I found the exact same thing: Bulging capacitors&lt;/p&gt;

&lt;p&gt;When you open up hardware you have to be careful about electric shocks. I never
received one, but especially power supply units (PSUs), which were the broken
parts inside of my displays, can store a high amount of energy for a long time
after disconnecting them from power.&lt;/p&gt;

&lt;p&gt;I didn’t want to buy new capacitors, even though they are available for low
prices and you can even buy a full repair kit online for commonly failing
displays.  Instead I went through even older hardware in the house and
scavenged still functioning capacitors. Mostly you have to look out for three
things:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;New capacitor has &lt;a href=&quot;https://web.archive.org/web/20160204013821/https://www.niccomp.com/help/capsubguide.asp&quot;&gt;correct characteristics&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;New capacitor fits into space of the old one&lt;/li&gt;
  &lt;li&gt;Install the new capacitor the right way or more hardware might blow up
(negative stripe on capacitor to white mark on board)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once I found replacement capacitors I desoldered the broken ones and replaced
them. Surprisingly these capacitors from TVs from the 80s still work just fine.
My plan was to buy brand-new capacitors once they fail, but it never happened.&lt;/p&gt;

&lt;p&gt;In hindsight it’s certainly curious to repair old hardware with parts from even
older hardware.&lt;/p&gt;

&lt;h2 id=&quot;power-supply-units&quot;&gt;Power Supply Units&lt;/h2&gt;

&lt;p&gt;The same story didn’t just happen with the PSUs inside of both of my displays,
but also with the power supply unit of my computer. I fixed it in the same way
and the PSU still works to this day. But since my LC Power LC6420 was never
very efficient I bought a replacement anyway, the more efficient HuntKey Jumper
300G.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;halfimg&quot; src=&quot;/public/hardware/lc-power-lc-6420.jpg&quot; alt=&quot;LC Power LC 6420&quot; /&gt;&lt;img class=&quot;halfimg&quot; src=&quot;/public/hardware/lc-power-lc-6420-2.jpg&quot; alt=&quot;Inside of the LC 6420&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately the new PSU turned out to be too loud for my taste, so I opened
it up immediately and noticed that the fan was fixed to 12 V. I reconnected it
to 5 V instead and tested that the PSU does not overheat with my system even at
high load. I wouldn’t recommend doing this since it immediately voids the
warranty and the PSU can overheat easily. Ideally just buy a modern PSU which
controls its fan properly based on the internal temperature.&lt;/p&gt;

&lt;h2 id=&quot;computer-case&quot;&gt;Computer Case&lt;/h2&gt;

&lt;p&gt;&lt;img class=&quot;halfimg&quot; src=&quot;/public/hardware/nzxt-zero.jpg&quot; alt=&quot;NZXT Zero case&quot; /&gt;&lt;img class=&quot;halfimg&quot; src=&quot;/public/hardware/molex.jpg&quot; alt=&quot;Molex extension cable&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I wasn’t just annoyed with the noise of my PSU though. In the same vein I also
switched all 7 case fans of my NZXT Zero from 12 V to 5 V, thus reducing the
amount of noise significantly. The fix is simple and does not require any
soldering. Instead I used a molex extension cable for the fans and switched its
12 V line to 5 V&lt;/p&gt;

&lt;p&gt;The trick here is to remove the pins from the molex cable with a small
screwdriver, and switch them around so that 12 V and 5 V are switched, which is
quite simple. I just had to make sure that my computer stays cool enough, but
with 9 fans in total running inside of it that was no problem, even in the
nearly noiseless state of running at 5 V.&lt;/p&gt;

&lt;p&gt;The CPU cooler can be controlled by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fancontrol&lt;/code&gt; on Linux and only spins up
when the load is high, otherwise the CPU can be cooled passively, just using
the slight flow of air from the case fans.&lt;/p&gt;

&lt;h2 id=&quot;random-access-memory&quot;&gt;Random Access Memory&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/public/hardware/ram-ddr3.jpg&quot; alt=&quot;DDR3 RAM&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Finally let’s get to an entirely different problem, for which I didn’t even
suspect a hardware fault: I’ve been using Gentoo for nearly the entire time,
which means I ended up compiling my own programs and libraries. Compiling a single
large-scale C++ library failed again and again, at seemingly random positions.
The g++ compiler always complained about internal compiler errors. At first I
assumed this was a bug in the GNU Compiler Collection, so I switched to other
versions, but got the same error in each of them. At some point I noticed that
the memory usage increases to multiple GBs when compiling this library and
that’s when it hit me: My main memory might be defective and with most programs
I just don’t notice.&lt;/p&gt;

&lt;p&gt;I booted into Memtest86+ to run the memory benchmark and, lo and behold, a
single byte of my RAM was broken. These were my choices on how to fix this:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Remove the RAM module, but then I don’t get the speed benefit of dual
channel and am missing out on 2 GB of RAM&lt;/li&gt;
  &lt;li&gt;Buy a replacement module, but that costs money&lt;/li&gt;
  &lt;li&gt;Tell the Linux kernel to ignore the broken part of the memory and use the rest of the module&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Of course I went with choice 3. A simple Linux kernel parameter
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;memmap=1$0x0007cec2d74&lt;/code&gt; in GRUB marks 1 byte at the address &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0x0007cec2d74&lt;/code&gt; as
reserved (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$&lt;/code&gt;), preventing the system from using it as regular main memory. A
reboot later everything worked again and I could compile the C++ library. It’s
possible that further parts of this memory module will break in the future and
you have to repeat this process to disable them as well, but so far this has
not happened.&lt;/p&gt;

&lt;p&gt;Working around hardware problems with software is cool. And it’s not like I
notice a single byte of memory missing.&lt;/p&gt;

&lt;h2 id=&quot;graphics-processing-units&quot;&gt;Graphics Processing Units&lt;/h2&gt;

&lt;p&gt;&lt;img class=&quot;halfimg&quot; src=&quot;/public/hardware/geforce-8500-gt.jpg&quot; alt=&quot;GeForce 8500 GT&quot; /&gt;&lt;img class=&quot;halfimg&quot; src=&quot;/public/hardware/radeon-hd4350.jpg&quot; alt=&quot;Radeon HD 4350&quot; /&gt;&lt;/p&gt;

&lt;p&gt;My graphics cards like to break. I don’t really play much except for some 2D
games, so a good GPU never mattered to me. The GeForce 8500 GT started to show
graphics errors after a few years, even while booting, which led me to believe
that the problem was on the hardware side and not software related.&lt;/p&gt;

&lt;p&gt;Putting the GPU into an oven and baking it for a while fixed the problem for a
few days by resoldering cracked solder points, but then it returned. This is
probably related to the switch to lead-free solder in 2006, which is more
brittle.&lt;/p&gt;

&lt;p&gt;I didn’t want to keep baking my GPU, so I used another GPU I had lying around,
which someone else had thrown away: A Radeon HD 4350&lt;/p&gt;

&lt;h2 id=&quot;mouse&quot;&gt;Mouse&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/public/hardware/logitech-mx-518.jpg&quot; alt=&quot;Logitech MX518&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The Logitech MX518 did its job great for the entire time. About 1-2 years ago
the left mouse button stopped working reliably though. When keeping it pressed
it sometimes released on its own at unpredictable moments. That’s a huge
problem while playing something like &lt;a href=&quot;https://ddnet.org/&quot;&gt;DDNet&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The first step was of course to open up the mouse. What I saw was that the
plastic of the mouse buttons was worn out to the point that it could barely
press down the actual button inside of the mouse. I reinforced both mouse
buttons with a small piece of thin cardboard. Since then the mouse has not made
any further problems and the buttons work absolutely reliably.&lt;/p&gt;

&lt;h2 id=&quot;keyboard&quot;&gt;Keyboard&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/public/hardware/logitech-ultra-flat-x.jpg&quot; alt=&quot;Logitech Ultra Flat-X&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I’m using a cheap Logitech Ultra Flat-X keyboard, which doesn’t feel terribly
different from a good Thinkpad keyboard. I replaced the green LEDs with blue
ones, but that was just an aesthetic change and was easily done with some more
soldering.&lt;/p&gt;

&lt;p&gt;Unfortunately dust and dirt always accumulate under the keys of a keyboard. If
this keeps going for long enough some keys become unusable, so I took out all
the key caps every year or so to wash them and clean the space below them.
Unfortunately the scissor mechanism on cheap keyboards is quite fragile, so you
have to be careful not to break your keys. I don’t need a numpad, so I used the
numpad keys as replacements.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/public/hardware/keycaps.jpg&quot; alt=&quot;Keyboard with keys sanded off&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After a few years of usage the key labels wear off. So instead I sanded the
key labels off entirely, leaving clear key caps which look much better even
after many more years of usage. Unfortunately by now the key caps have become
so thin that they are starting to break apart, so it’s definitely time for a new
keyboard.&lt;/p&gt;

&lt;h2 id=&quot;printer&quot;&gt;Printer&lt;/h2&gt;

&lt;p&gt;&lt;img style=&quot;max-width: 70%; display: inline; padding: 0;&quot; src=&quot;/public/hardware/lj4l.jpg&quot; alt=&quot;HP LaserJet 4L&quot; /&gt;&lt;img style=&quot;max-width: 30%; display: inline; padding: 0 0 0 1mm;&quot; src=&quot;/public/hardware/pickup-roller.jpg&quot; alt=&quot;Pickup Roller&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This is the oldest piece of hardware in the list so far. This HP LaserJet 4L
has been in continuous use for about 23 years. The only serious thing you need
to worry about with it is to have fresh toner cartridges if you plan to print
extensively. Luckily I got 10 full toner cartridges on eBay for 1 €, taking
care of this problem.&lt;/p&gt;

&lt;p&gt;The only part of the printer I had to replace was the pickup roller, which
became unable to pull in any pages. I tried cleaning it, but in the end decided
to just buy a cheap replacement part, which is easy to find in online shops,
even 23 years after the introduction of the printer.&lt;/p&gt;

&lt;h2 id=&quot;headphones&quot;&gt;Headphones&lt;/h2&gt;

&lt;p&gt;&lt;img style=&quot;max-width: 40%; display: inline; padding: 0;&quot; src=&quot;/public/hardware/superlux-hd-330.jpg&quot; alt=&quot;Superlux HD-330&quot; /&gt;&lt;img style=&quot;max-width: 60%; display: inline; padding: 0 0 0 1mm;&quot; src=&quot;/public/hardware/beyerdynamic-ear-pads.jpg&quot; alt=&quot;Beyerdynamic Ear Pads&quot; /&gt;&lt;/p&gt;

&lt;p&gt;My headphones were not really broken, but rather are a chimera. The base are
cheap Superlux HD-330 headphones (29 €), a not-so-subtle clone of the
Beyerdynamic DT-770 Pro (137 €).&lt;/p&gt;

&lt;p&gt;Nevertheless they still sound fine to me, but I don’t claim to be an expert.
Since I didn’t like the cable I used the old nylon cable from my previous
headphones and soldered them in as replacements. A little bit of soldering gets
you quite far.&lt;/p&gt;

&lt;p&gt;Instead of using the included fake leather ear pads I bought the original ear
pads from Beyerdynamic (21 €), which cost nearly as much as the rest of the
headphones.&lt;/p&gt;

&lt;h2 id=&quot;amplifier&quot;&gt;Amplifier&lt;/h2&gt;

&lt;p&gt;&lt;img style=&quot;max-width: 70%; display: inline; padding: 0;&quot; src=&quot;/public/hardware/marantz-pm710dc.jpg&quot; alt=&quot;Marantz PM710DC&quot; /&gt;&lt;img style=&quot;max-width: 30%; display: inline; padding: 0 0 0 1mm;&quot; src=&quot;/public/hardware/heco-superior-700.jpg&quot; alt=&quot;Heco Superior 700&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Finally here’s the oldest part of my setup: A Marantz PM710DC amplifier from
1980 and HECO Superior 700 speakers from around 1985. The speakers still sound
just fine. The amplifier on the other hand is showing its age:&lt;/p&gt;

&lt;p&gt;The analog potentiometers are filled with dust and you hear a crackling sound
when adjusting the volume. Blowing on them with some compressed air and moving
the potentiometer a few times entirely removes the crackling for me. In more
serious cases potentiometers can also be cleaned with contact cleaner or simply
be replaced with some soldering.&lt;/p&gt;

&lt;h2 id=&quot;desk-light&quot;&gt;Desk Light&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/public/hardware/ikea-global-work-lamp.jpg&quot; alt=&quot;IKEA Global in working state&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The IKEA Global work lamp is quite useful, as it can be attached to many
surfaces with a clamp and can be moved flexibly. Unfortunately all the flexible
movement kept weakening the plastic connection between lamp and metal arm. In the
end the plastic simply snapped off. My plastic glue didn’t work with this kind
of plastic, so I decided to work around the problem instead.&lt;/p&gt;

&lt;p&gt;Using a few pieces of paper as a buffer the lamp can be inserted into the clamp
directly. Now I have the lamp fixed directly above the desk instead of being
able to move it around freely, but that’s not a big problem because the entire
desk is still well-lit.&lt;/p&gt;

&lt;h2 id=&quot;network-cables&quot;&gt;Network Cables&lt;/h2&gt;

&lt;p&gt;&lt;img style=&quot;max-width: 33%; display: inline; padding: 0;&quot; src=&quot;/public/hardware/cat5e-roll.jpg&quot; alt=&quot;100 m CAT5e roll&quot; /&gt;&lt;img style=&quot;max-width: 33%; display: inline; padding: 0;&quot; src=&quot;/public/hardware/rj45-jack.jpg&quot; alt=&quot;RJ45 Jack&quot; /&gt;&lt;img style=&quot;max-width: 33%; display: inline; padding: 0;&quot; src=&quot;/public/hardware/crimping-tool.jpg&quot; alt=&quot;Crimping Tool&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I used to have lots of old Ethernet cables, some working only up to 100 Mbit/s,
others not working at all at times. Instead of buying many new Ethernet cables,
I opted to buy a 100 meter roll of high quality CAT5e Ethernet cable, a
hundred RJ45 jacks and a crimper tool. Finally I can build as many cables as I
need with the exact lengths I prefer.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/public/hardware/cables.jpg&quot; alt=&quot;Using newly made network cables&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;laptops&quot;&gt;Laptops&lt;/h2&gt;

&lt;p&gt;&lt;img class=&quot;halfimg&quot; src=&quot;/public/hardware/t43p.jpg&quot; alt=&quot;Thinkpad T43p&quot; /&gt;&lt;img class=&quot;halfimg&quot; src=&quot;/public/hardware/t43p-fan.jpg&quot; alt=&quot;Thinkpad T43p Fan&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Initially I had a used Thinkpad T43p from 2005. After a few years of running
Gentoo on it and compiling too many packages the fan broke. Fortunately
replacement fans are easy to find and opening up and repairing old Thinkpads is
a blessing. Printed on the bottom of the laptop you are instructed how to open
which part of the laptop. There are official guides for replacing any part of
the laptop.&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;halfimg&quot; src=&quot;/public/hardware/x200s.jpg&quot; alt=&quot;Thinkpad x200s&quot; /&gt;&lt;img class=&quot;halfimg&quot; src=&quot;/public/hardware/x200s-battery.jpg&quot; alt=&quot;Thinkpad x200s Battery&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Since 2010 I’m running a used Thinkpad x200s. The only problem it ever had was
a broken battery. But since the laptop features replaceable batteries installing
a cheap replacement battery from eBay is a matter of seconds.&lt;/p&gt;

&lt;h2 id=&quot;home-servers&quot;&gt;Home Servers&lt;/h2&gt;

&lt;p&gt;&lt;img class=&quot;halfimg&quot; src=&quot;/public/hardware/server.jpg&quot; alt=&quot;Thinkpad T42 Server&quot; /&gt;&lt;img class=&quot;halfimg&quot; src=&quot;/public/q1900-itx.jpg&quot; alt=&quot;Q1900-ITX Server&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Broken Thinkpads make good servers. I had an old, very broken Thinkpad T42
lying around. The screen did not work and it could only be powered from the
battery, while it didn’t run on AC power at all and couldn’t charge the battery
either. My solution was to remove the screen entirely and build a small adapter
with some soldering to connect AC power to the battery connection directly,
leaving out the battery.&lt;/p&gt;

&lt;p&gt;This worked as a home server for a few years, until the performance of such an
old Pentium M system got too low and I switched to a totally passive Q1900-ITX
system in 2014.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;All in all I would say that the hardware held up reasonably well and when it
broke it was usually possible to fix it without replacing the entire device.
More repairable hardware would still be a huge bonus, especially since the
industry has been moving in the opposite direction with ever smaller and more
compacted devices.&lt;/p&gt;

&lt;p&gt;Maybe someone who read this post found it interesting and will have fun fixing
their own hardware (or trying to) once it starts malfunctioning.&lt;/p&gt;

&lt;p&gt;I wrote this up on the exact hardware described in this post, while waiting for
my new hardware to arrive in the next days. I hope build quality and
repairability of PC hardware haven’t gone down in the last years. But only time
will tell.&lt;/p&gt;

&lt;p&gt;Discussion on &lt;a href=&quot;https://news.ycombinator.com/item?id=11942618&quot;&gt;Hacker News&lt;/a&gt; and &lt;a href=&quot;https://www.reddit.com/r/hardware/comments/4pap8e/broken_hardware_fixes_and_hacks_over_8_years/&quot;&gt;r/Hardware&lt;/a&gt;.&lt;/p&gt;

   </content>
 </entry>
 
 <entry>
   <title>Experiences of Running an Online Game for 3 Years</title>
   <link href="https://hookrace.net/blog/ddnet-evolution-architecture-technology/"/>
   <updated>2016-06-09T00:00:00-04:00</updated>
   <id>https://hookrace.net/blog/ddnet-evolution-architecture-technology</id>
   <content type="html">
     &lt;p&gt;It’s been roughly 3 years since &lt;a href=&quot;https://ddnet.org/&quot;&gt;DDraceNetwork&lt;/a&gt; (DDNet)
started in the summer of 2013. Last year I wrote a non-technical &lt;a href=&quot;https://forum.ddnet.org/viewtopic.php?f=3&amp;amp;t=1824&quot;&gt;History of
DDNet&lt;/a&gt;. Today in this post we
will dive into the technical side of what makes DDNet run.&lt;/p&gt;

&lt;p&gt;For the uninitiated, DDNet is an &lt;a href=&quot;https://github.com/ddnet/ddnet&quot;&gt;open-source
modification&lt;/a&gt; of
&lt;a href=&quot;https://www.teeworlds.com/&quot;&gt;Teeworlds&lt;/a&gt;, a retro multiplayer shooter. DDNet is
a game in which you, instead of killing each other, cooperate with each other
to work your way through challenging maps, trying to beat the map or get a
better time than other teams.&lt;/p&gt;

&lt;!--more--&gt;
&lt;p&gt;
  &lt;div class=&quot;video-container&quot;&gt;
    &lt;iframe src=&quot;https://www.youtube.com/embed/xwyk4hPZM1g&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
  &lt;/div&gt;
&lt;/p&gt;

&lt;p&gt;What we offer for DDNet is the &lt;a href=&quot;https://ddnet.org/downloads/&quot;&gt;client and server
software&lt;/a&gt; as well as official servers that run all
around the world. We have an international community of players trying to beat
the latest maps and records, and dozens of mappers who send in their newest
creations for our consideration. A new map is &lt;a href=&quot;https://ddnet.org/releases/&quot;&gt;released every few
days&lt;/a&gt; and occasionally
&lt;a href=&quot;https://ddnet.org/tournaments/&quot;&gt;tournaments&lt;/a&gt; happen in which the best compete
against each other on brand-new maps.&lt;/p&gt;

&lt;p&gt;At the time of writing there are 968 people playing Teeworlds, 678 of which are
on a server running the DDNet mod, and 430 on the official DDNet servers. So
this project is not especially big and not many people stumble upon DDNet. Our
player base consists in a large part of old Teeworlds players and their friends
who heard about us by word of mouth.&lt;/p&gt;

&lt;p&gt;Nevertheless I hope that the technical challenges we faced and face as well as
our solutions are interesting. Let’s start with an overview of what we have:&lt;/p&gt;

&lt;h2 id=&quot;overview--financing&quot;&gt;Overview &amp;amp; Financing&lt;/h2&gt;

&lt;p&gt;DDNet runs official servers in Germany, Russia, USA, Canada, Chile, Brazil,
China, South Africa and Iran.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/public/locations.png&quot; alt=&quot;DDNet Locations&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Since DDNet is totally free, has no advertisements and no in-game purchases, it
offers no stream of revenue and is solely &lt;a href=&quot;https://ddnet.org/funding/&quot;&gt;funded by donations, donated servers
and my own money&lt;/a&gt;. So one of the goals is to keep
costs down: On average we pay 10 € per month for each location.&lt;/p&gt;

&lt;p&gt;Let’s see what we can offer with this to our thousands of players (&lt;a href=&quot;https://ddnet.org/stats/&quot;&gt;detailed
statistics&lt;/a&gt;) and how DDNet evolved to keep with
increasing number of players, maps and the product of both, ranks.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://ddnet.org/funding/&quot;&gt;&lt;img src=&quot;/public/stats-finishes.png&quot; alt=&quot;Number of finishes&quot; /&gt;&lt;/a&gt;
&lt;a href=&quot;https://ddnet.org/funding/&quot;&gt;&lt;img src=&quot;/public/stats-players.png&quot; alt=&quot;Number of players&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;servers--locations&quot;&gt;Servers &amp;amp; Locations&lt;/h2&gt;

&lt;p&gt;We exclusively use cheap virtual private servers (VPS). They offer enough
performance for us and are significantly cheaper than dedicated servers. To
have a bit stronger guarantees on performance I prefer KVM and XEN over OpenVZ,
but there are good and bad hosters for each. In the end you always have to try
out a hoster for a few days up to a month to find out if it is suitable for
hosting official DDNet servers. The most important criteria for us are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Good and consistent CPU performance: should do about 400 players per CPU core&lt;/li&gt;
  &lt;li&gt;Low network latency to our players in the region: DDNet is only enjoyable
with a low and stable ping&lt;/li&gt;
  &lt;li&gt;0.5 to 1 GB of RAM is enough for &lt;del&gt;everyone&lt;/del&gt; most locations: running
about 20 game servers per location&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When searching for hosters in a country you should prefer to look in the
language of the country instead of English. These tend to be cheaper and less
overcrowded by international buyers. Be prepared to use a translator and have
trouble with the support, but after some time all these hoster websites start
to look the same to you, no matter if they’re in English, German, French,
Spanish or Farsi.&lt;/p&gt;

&lt;p&gt;When in doubt, ask locals what hosters they use or recommend, or check the
whois entries for servers in the region that seem to run well. For example the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;whois 31.186.251.128&lt;/code&gt; entry tells you that &lt;a href=&quot;https://ddnet.org/&quot;&gt;DDNet.tw&lt;/a&gt; (and
this blog) are hosted at Nuclear Fallout Enterprise, connected by the InterNAP
Network:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;% This is the RIPE Database query service.
% The objects are in RPSL format.
%
% The RIPE Database is subject to Terms and Conditions.
% See http://www.ripe.net/db/support/db-terms-conditions.pdf

% Note: this output has been filtered.
%       To receive output for a database update, use the &quot;-B&quot; flag.

% Information related to &apos;31.186.250.0 - 31.186.251.255&apos;

% Abuse contact for &apos;31.186.250.0 - 31.186.251.255&apos; is &apos;noc@internap.com&apos;

inetnum:        31.186.250.0 - 31.186.251.255
netname:        INAP-LON-nuclearfallout-31-186-250-0
descr:          Nuclear Fallout Enterprise
country:        DE
admin-c:        INAP-RIPE
tech-c:         INAP-RIPE
status:         ASSIGNED PA
mnt-by:         INAP-MAINT-RIPE
created:        2013-08-08T19:09:47Z
last-modified:  2014-03-06T23:07:27Z
source:         RIPE # Filtered

person:         InterNAP Network Operator
address:        InterNAP Network Services
address:        Two Union Square
address:        601 Union Street Suite 1000
address:        Seattle, WA 98101 USA
phone:          +1 206 441 8800
fax-no:         +1 206 256 9580
nic-hdl:        INAP-RIPE
mnt-by:         INAP-MAINT-RIPE
created:        2002-03-04T12:57:06Z
last-modified:  2002-03-04T12:57:06Z
source:         RIPE # Filtered

% This query was served by the RIPE Database Query Service version 1.87.3 (DB-2)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Unfortunately the story doesn’t end here for us. Since DDNet is a multiplayer
open-source game we seem to attract the kind of people who know how to find a
DDoS (distributed denial of service) booter. This resulted in us being kicked
out at many hosters since they either do not have any (D)DoS protection or it
is not sufficient against some attacks, which then also affect other customers
of theirs.&lt;/p&gt;

&lt;p&gt;In the end we settled on &lt;a href=&quot;https://www.nfoservers.com/&quot;&gt;NFOservers&lt;/a&gt; for our main
servers, While they do not advertise any DDoS protection, they turned out to be
the only one of dozens of hosters I tried that can withstand most of the
attacks we get regularly. I’m of course talking about cheap VPSes, I hear that
good DDoS protection exists for a few hundred euros per month, which is far
outside of our reach. Unfortunately NFOservers is only available in USA and
Germany.&lt;/p&gt;

&lt;p&gt;Here’s what the attacks in a regular 2 week period looks like in our most
popular location, Europe (GER server):&lt;/p&gt;

&lt;p&gt;
  &lt;div style=&quot;height: 20em; overflow: scroll;&quot;&gt;
    &lt;a href=&quot;/public/ddos.png&quot;&gt;&lt;img src=&quot;/public/ddos.png&quot; alt=&quot;GER DDoS attacks&quot; /&gt;&lt;/a&gt;
  &lt;/div&gt;
&lt;/p&gt;

&lt;p&gt;In Russia (Moscow) we had ok experiences with &lt;a href=&quot;https://www.reg.com/&quot;&gt;reg.com&lt;/a&gt;,
at least the ping for local players is lower than in any other location.&lt;/p&gt;

&lt;p&gt;Recently we got a server hosted in Iran again, which has always been a
difficult country to host a server in, for matters of cost as well as their
preference of an intranet over the internet.&lt;/p&gt;

&lt;p&gt;In Chile we’re now with &lt;a href=&quot;http://www.zglobalhost.com/&quot;&gt;zGlobalHost&lt;/a&gt;, one of the
few hosters with unlimited bandwidth and by far the best I found for just 9 €
per month. Their server room looks very cozy:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/public/zglobalhost.jpg&quot; alt=&quot;ZGlobalHost data center&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In Chile we also had this &lt;a href=&quot;https://www.facebook.com/zglobalhost/&quot;&gt;bizarre
story&lt;/a&gt; back in January, after the entire
data center of ZGlobalHost went down:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;January 26, 16:04 (local time Chile)&lt;br /&gt;
Our provider GTD has trouble again and is still not giving estimated time of
repair. We will inform you with more news as soon as possible.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;January 26, 18:06&lt;br /&gt;
Our provider informs us that in the next 30 minutes everything should be
operational again.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;January 26, 19:09&lt;br /&gt;
GTD reports that there are two fiber cuts, one in Pudahuel and another in the
Eastern Zone. They are certifying the fibers and lifting services one by one.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;January 26, 22:34&lt;br /&gt;
Be informed that when we are back online and GTD reports that it will provide
further details about the incident as it was just reported that the fiber cut
was due to vandalism, the streets in Providencia are now reported to be safe
for repair works again.&lt;br /&gt;
We do not have backup lines. It will at least take another hour but
repairs should not take longer than until this morning. You will be
mailed a report instantly when the servers are back online and tomorrow
GTD will send a report with details.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;January 26, 22:36&lt;br /&gt;
The new details we have say that there were 4 cuts today, which is why
there was such a long delay:&lt;br /&gt;
Locations: Pudahuel, Providencia, La Florida, Macul&lt;br /&gt;
Causes for each cut:
Fire, Vandalism, Highway Accident, Material fatigue in electric pole cut it down&lt;br /&gt;
All ISPs in various areas have been affected, particularly fiber optic, both
business and home addresses.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;January 27, 03:00&lt;br /&gt;
Service is fully operational again since 02:30 AM.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To make sure that our servers keep running well, we &lt;a href=&quot;https://ddnet.org/status/&quot;&gt;monitor
them&lt;/a&gt; and record the &lt;a href=&quot;https://ddnet.org/stats/server/&quot;&gt;server
statistics&lt;/a&gt;. When an unexpected event happens,
like a server downtime or high network traffic, a notification is automatically
sent. You can read more about this system in &lt;a href=&quot;https://hookrace.net/blog/server-statistics/&quot;&gt;a separate post from last
month&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;software-platform&quot;&gt;Software Platform&lt;/h2&gt;

&lt;p&gt;As the operating system our servers run Linux, preferably
&lt;a href=&quot;https://www.debian.org/&quot;&gt;Debian&lt;/a&gt;, but also some instances of Ubuntu and
CentOS. In areas like South Africa, South America and Iran bandwidth can be
exorbitantly expensive, so when you get a donated server in one of those
locations you can not afford to be picky.&lt;/p&gt;

&lt;p&gt;For the databases we use &lt;a href=&quot;https://mariadb.org/&quot;&gt;MariaDB&lt;/a&gt;, the drop-in MySQL
fork started by the original MySQL developers after Oracle acquired it. We use
this database to record our maps, saved games and the ranks of all players
worldwide and keep these synchronized around the world.&lt;/p&gt;

&lt;p&gt;Initially we started with a single VPS and a database running directly on it.
As we grew to add more locations around the world we used a circular
master-slave replication. With time some servers turned out to be less stable
than others, so they were taken out of the loop, their game servers
communicating with nearby MariaDB servers instead:&lt;/p&gt;

&lt;p&gt;&lt;img class=&quot;halfimg&quot; src=&quot;/public/ddnet-replication-1.png&quot; alt=&quot;Old MariaDB Replication&quot; /&gt;&lt;img class=&quot;halfimg&quot; src=&quot;/public/ddnet-replication-2.png&quot; alt=&quot;New MariaDB Replication&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now we’re switching away from this circular master-slave replication to having
a small number of master databases which are replicated by slaves at each
location. This has the advantage that database reads happen locally, so getting
your own rank is always fast. Meanwhile new ranks are sent to a master server
when possible or otherwise stored in a local file to be added later. This is
necessary since our cheap servers are not always connected to the entire
internet, sometimes they are only available from inside their own country.&lt;/p&gt;

&lt;p&gt;The DDNet client and server, being based on Teeworlds, are also written in
C/C++ (also known as C with classes). Luckily their performance was good from
the start, but a few optimizations still helped with keeping the load low when
running up to 60 game servers on a single VPS, for example:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Reduce the number of syscalls by caching the value of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gettimeofday()&lt;/code&gt; until
a new tick happens or network packet comes in&lt;/li&gt;
  &lt;li&gt;Do not execute the main game loop when the server is currently empty, as
nothing will happen with no players anyway&lt;/li&gt;
  &lt;li&gt;Use CPU (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nice&lt;/code&gt;) and IO (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ionice&lt;/code&gt;) priorities to prevent background tasks
(lower priority) to get in the way of game server processes (high priority)&lt;/li&gt;
  &lt;li&gt;Each VPS compiles its own server binary tuned for the specific target
architecture&lt;/li&gt;
  &lt;li&gt;Prefer to download map files over HTTP from our web server instead of the
built-in UDP protocol that can be shaky and slow. This also reduces the
network traffic on game servers, keeping network jitter lower.&lt;/li&gt;
  &lt;li&gt;Instead of downloading the entire archive on a new client release, update
only the changed files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Generally the idea is to wait until you notice that some resource is being used
too much, benchmark and analyze what the reason for it is, and only then
optimize this.&lt;/p&gt;

&lt;h2 id=&quot;website&quot;&gt;Website&lt;/h2&gt;

&lt;p&gt;Back when DDNet started in 2013 the website ran on the same VPS as our game
servers. Later we switched to hosting the website on a separate VPS, in part to
improve the DDoS protection by having mostly UDP on game servers and mostly TCP
on the web server, but also to make sure that the website traffic and CPU usage
do not impact the game servers.&lt;/p&gt;

&lt;p&gt;This has become even more important since the map downloads are now also
happening over HTTP from the web server instead of going over UDP from the game
servers. Automatically updating the list of available maps is done with a small
Nim script that I already presented in &lt;a href=&quot;/blog/what-makes-nim-practical/#use-as-a-scripting-language&quot;&gt;a previous
article&lt;/a&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-nimrod&quot; data-lang=&quot;nimrod&quot;&gt;&lt;span class=&quot;c&quot;&gt;#!/usr/bin/env nimscript&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;crc32&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strutils&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;baseDir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/home/teeworlds/servers&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;mapdlDir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/var/www-maps&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;walkDir&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;baseDir&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;maps&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kind&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pcFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ext&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;splitFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ext&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;.map&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;sum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;crc32FromFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;toHex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;toLower&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;newName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;_&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ext&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;newPath&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mapdlDir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newName&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tmpPath&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newPath&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;.tmp&quot;&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;existsFile&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;copyFile&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tmpPath&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;moveFile&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tmpPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newPath&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Most of the &lt;a href=&quot;https://ddnet.org/&quot;&gt;DDNet.tw&lt;/a&gt; website is &lt;a href=&quot;https://github.com/ddnet/ddnet-web&quot;&gt;statically built by
jekyll&lt;/a&gt; and automatically deploying using
GitHub’s webhooks. That’s a simple solution and requires very few resources.&lt;/p&gt;

&lt;p&gt;Pages about &lt;a href=&quot;https://ddnet.org/status/&quot;&gt;player status&lt;/a&gt;,
&lt;a href=&quot;https://ddnet.org/ranks/&quot;&gt;ranks&lt;/a&gt;, &lt;a href=&quot;https://ddnet.org/releases/&quot;&gt;map releases&lt;/a&gt;
and &lt;a href=&quot;https://ddnet.org/mappers/&quot;&gt;mappers&lt;/a&gt; are also statically built, but by
&lt;a href=&quot;https://github.com/ddnet/ddnet-scripts/tree/master/servers/scripts&quot;&gt;Python
scripts&lt;/a&gt;
directly on the server. This also makes sense since these pages are expensive
to build and are requested more often than they have to be generated.&lt;/p&gt;

&lt;p&gt;We also used to generate the &lt;a href=&quot;https://ddnet.org/players/&quot;&gt;player pages&lt;/a&gt; in the
same way, but since we have about 90,000 ranked players so far this would’ve
become a bit too expensive. Instead we run a small uWSGI Python server to
dynamically generate them now from the data kept in memory. Right now it takes
about 40-80 ms to generate a single player page, by far fast enough for us. If
the computation time goes up or we suddenly become much more popular caching
could be implemented.&lt;/p&gt;

&lt;p&gt;We used to package a growing selection of skins with the DDNet client, but
maintaining this became too cumbersome and increased memory usage for the
client. Now instead the website offers a &lt;a href=&quot;https://ddnet.org/skins/&quot;&gt;database of
skins&lt;/a&gt; where you can select individual skins or skin
packs.&lt;/p&gt;

&lt;h2 id=&quot;software-releases&quot;&gt;Software Releases&lt;/h2&gt;

&lt;p&gt;DDNet has been in active development for the last 3 years, with our first
client &amp;amp; server software release back in &lt;a href=&quot;https://github.com/ddnet/ddnet/releases/tag/1.18&quot;&gt;October 9,
2013&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Initially I used to build the releases manually using virtual machines for
Windows and Linux, later also Mac OS X. After some time this got tedious of
course, so I wrote an &lt;a href=&quot;https://github.com/ddnet/ddnet-scripts/blob/master/ddnet-release.sh&quot;&gt;automated build
script&lt;/a&gt;.
It uses a Debian chroot for Linux building, MinGW for Windows and a QEMU VM for
Mac OS X. I set up the Mac OS X QEMU image using &lt;a href=&quot;http://www.contrib.andrew.cmu.edu/~somlo/OSXKVM/&quot;&gt;a continuously updated
guide&lt;/a&gt; and it works just
fine. I connect to the guest OS through a simple ssh connection that is
forwarded to the virtual machine:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Start the Mac OS X VM&lt;/span&gt;
qemu-system-x86_64 &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;...] &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-netdev&lt;/span&gt; user,id&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;hub0port0,hostfwd&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;tcp::10022-:22 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-device&lt;/span&gt; e1000-82545em,netdev&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;hub0port0,id&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;mac_vnet0 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &amp;amp;&amp;gt;/dev/null &amp;amp;

&lt;span class=&quot;c&quot;&gt;# Wait until VM is booted up&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt; ssh &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; 10022 &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;ConnectTimeout&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;10 localhost &lt;span class=&quot;nb&quot;&gt;exit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Run build script&lt;/span&gt;
ssh &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; 10022 localhost &lt;span class=&quot;s2&quot;&gt;&quot;# Run build script for Mac OS X&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Building for Android is also possible but disabled right now because we
still have to port the Android version to SDL2, and DDNet is not really
playable on Android anyway since a mouse is the most important tool in the
game.&lt;/p&gt;

&lt;p&gt;When you think about it, it’s pretty amazing that you can easily cross-compile
on a single machine for all of these targets:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Windows x86, x86_64&lt;/li&gt;
  &lt;li&gt;Linux x86, x86_64&lt;/li&gt;
  &lt;li&gt;Mac OS X&lt;/li&gt;
  &lt;li&gt;Android armeabi, armeabi-v7a, mips, x86&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The automated builds happen on my cheap ASRock Q1900-ITX home sever (which was
also used to &lt;a href=&quot;/blog/ddnet-live/&quot;&gt;stream gameplay from DDNet servers&lt;/a&gt;) and
still only take a few minutes to complete:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/public/q1900-itx.jpg&quot; alt=&quot;Home server&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Finally when all goes well you get a small summary of the build times that
looks like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Preparation:      10 s
Linux x86_64:     62 s
Linux x86:        64 s
Windows x86_64:  106 s
Windows x86:      84 s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For the automated builds we use bundled static libraries, while people who
build our software locally probably prefer to use their system libraries. Using
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pkg-config&lt;/code&gt; we detect whether libraries are available at compile-time and
otherwise use the static ones if they are available. This behaviour can be
controlled manually as well:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;# Analog to ./configure&lt;/span&gt;
bam config curl.use_pkgconfig&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
           opus.use_pkgconfig&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
           opusfile.use_pkgconfig&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
           ogg.use_pkgconfig&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Analog to make&lt;/span&gt;
bam release&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Even a &lt;a href=&quot;http://teewebs.net/&quot;&gt;JavaScript port&lt;/a&gt; of DDNet client is available for
playing without any installation, compiled with Emscripten. For this purpose
the official servers also accept WebSocket connections additionally to UDP.&lt;/p&gt;

&lt;p&gt;New releases are added to the &lt;a href=&quot;https://ddnet.org/downloads/&quot;&gt;website&lt;/a&gt; with a
nice changelog. But most existing players get their update notification in the
client directly and run the updater from there, only downloading the files that
actually changed instead of the entire new release.&lt;/p&gt;

&lt;h2 id=&quot;map-testing--releases&quot;&gt;Map Testing &amp;amp; Releases&lt;/h2&gt;

&lt;p&gt;The best part about DDNet is its active community. New maps for our servers are
regularly sent in to be tested and later released. Initially the testing
process was implemented as &lt;a href=&quot;https://trac.edgewall.org/&quot;&gt;Trac&lt;/a&gt; installation. But
it turned out that the non-technical people did not appreciate it much, so we
switched to a &lt;a href=&quot;https://forum.ddnet.org/&quot;&gt;regular phpBB forum&lt;/a&gt; for testing and
communication.&lt;/p&gt;

&lt;p&gt;New maps can be uploaded by our testers to the test servers and then tested
there. The map upload happens with an upload script to the web server. The test
servers around the world use sshfs to access these maps.&lt;/p&gt;

&lt;p&gt;Once a new map has been cleared by the testers and all bugs fixed by the
mapper, it can be
&lt;a href=&quot;https://github.com/ddnet/ddnet-scripts/blob/master/map-release&quot;&gt;released&lt;/a&gt; on
the proper server. The preparation for this happens on the web server and is
then synchronized to the other servers using an internal git repository.&lt;/p&gt;

&lt;p&gt;The new map is also announced on the &lt;a href=&quot;https://ddnet.org/releases/&quot;&gt;recent map releases
page&lt;/a&gt; as well as its feed. Players on the official
servers are informed about the map release using a broadcast message.&lt;/p&gt;

&lt;p&gt;It’s possible to take a look at our maps using a WebGL renderer that supports
parts of the map format, but no animations for example: &lt;a href=&quot;https://ddnet.org/maps/?map=Lonely&quot;&gt;Lonely
map&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When a popular new map is released, it’s possible to have hundreds of players
taking up the challenge of playing the new map at once, either in small teams
or in big groups.&lt;/p&gt;

&lt;p&gt;Finally every day the newly released maps are added to our &lt;a href=&quot;https://github.com/ddnet/ddnet-maps&quot;&gt;public maps
repository&lt;/a&gt;,
which can be used to easily run a clone of the official DDNet servers at home or
on your own server. Just download the repository, add the DDNet-Server binary
to the same directory and run it. A simple shell script suffices to update this
repository:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;#!/usr/bin/env zsh&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; /home/teeworlds/ddnet-maps
&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /home/teeworlds/ddnet-maps
&lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-rf&lt;/span&gt; types

&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;add_path $USERDIR\nadd_path $CURRENTDIR&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; storage.cfg

&lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;i &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; ../servers/all-types&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do 
  &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;TYPEDIR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;types/&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;i&lt;/span&gt;:l&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;add_path &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$TYPEDIR&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; storage.cfg
  &lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$TYPEDIR&lt;/span&gt;/maps
  &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;|&quot;&lt;/span&gt; ../servers/&lt;span class=&quot;nv&quot;&gt;$TYPEDIR&lt;/span&gt;/maps | &lt;span class=&quot;nb&quot;&gt;cut&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;|&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f2&lt;/span&gt; | &lt;span class=&quot;k&quot;&gt;while &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;read &lt;/span&gt;j&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do 
    &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;../servers/maps/&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$j&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.map&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$TYPEDIR&lt;/span&gt;/maps
    &lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; ../servers/&lt;span class=&quot;nv&quot;&gt;$TYPEDIR&lt;/span&gt;/flexvotes.cfg &lt;span class=&quot;nv&quot;&gt;$TYPEDIR&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;flexname.cfg&quot;&lt;/span&gt; ../servers/&lt;span class=&quot;nv&quot;&gt;$TYPEDIR&lt;/span&gt;/flexreset.cfg &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$TYPEDIR&lt;/span&gt;/flexreset.cfg
    &lt;span class=&quot;nb&quot;&gt;tail&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; +5 ../servers/&lt;span class=&quot;nv&quot;&gt;$TYPEDIR&lt;/span&gt;/votes.cfg &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$TYPEDIR&lt;/span&gt;/votes.cfg
  &lt;span class=&quot;k&quot;&gt;done
done

&lt;/span&gt;git add &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; &amp;amp;&amp;gt;/dev/null
git commit &lt;span class=&quot;nt&quot;&gt;-a&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;daily update&quot;&lt;/span&gt; &amp;amp;&amp;gt;/dev/null
git push &amp;amp;&amp;gt;/dev/null&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;server-types&quot;&gt;Server Types&lt;/h2&gt;

&lt;p&gt;In the start DDNet hosted only new maps categorized by their difficulty:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://ddnet.org/ranks/novice/&quot;&gt;Novice&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://ddnet.org/ranks/moderate/&quot;&gt;Moderate&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://ddnet.org/ranks/brutal/&quot;&gt;Brutal&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Later we also added categories for maps from old servers:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://ddnet.org/ranks/oldschool/&quot;&gt;Oldschool&lt;/a&gt;: Ancient maps&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://ddnet.org/ranks/ddmax/&quot;&gt;DDmaX&lt;/a&gt;: Old DDRace server before DDNet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As well as categories for different kinds of single player maps:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://ddnet.org/ranks/solo/&quot;&gt;Solo&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://ddnet.org/ranks/race/&quot;&gt;Race&lt;/a&gt;: Classic mod with fewer features,
converted maps to new DDNet format&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://ddnet.org/ranks/dummy/&quot;&gt;Dummy&lt;/a&gt;: Maps where you play with another
player that is unable to move&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The idea is that, since we are the major hoster in many countries, many of
these good maps would be forgotten forever if we don’t host them. At the same
time we try to motivate people to make creative new maps, having worked
together with mappers to add new features that they require.&lt;/p&gt;

&lt;p&gt;While the DDNet servers are the main focus, we also use our servers for hosting
a few other Teeworlds mods since they use very few resources and the servers
are running already anyway.&lt;/p&gt;

&lt;h2 id=&quot;tournaments&quot;&gt;Tournaments&lt;/h2&gt;

&lt;p&gt;When a well-known mapper makes a special map, they can choose to have their map
played at an official DDNet &lt;a href=&quot;https://ddnet.org/tournaments/&quot;&gt;tournament&lt;/a&gt;. For
this it is necessary to keep the map under wraps, showing it only to a few
select testers. This ensures that none of the players at the tournament will be
familiar with the map already, which would give them an unfair advantage.&lt;/p&gt;

&lt;p&gt;Usually tournaments are done on Sunday, 20:00 Central European Time since most
of our player base is located in Europe. You can read about the work that goes
into &lt;a href=&quot;https://github.com/ddnet/ddnet-scripts/blob/master/tournament-preparation&quot;&gt;preparing a
tournament&lt;/a&gt;.
But the really stressful part is actually holding the tournament.&lt;/p&gt;

&lt;p&gt;Since it runs at the same time on all (currently 9-10) servers worldwide, you
need to control them all at once. If the tournament map has not been tested
well enough it might need to be quickly fixed and reloaded during the
tournament. Sometimes problems occur on a single server. Unfortunately
tournaments also attract DDoS attacks.&lt;/p&gt;

&lt;p&gt;Here you can see the last tournament’s top run, for which the players had 2
hours to compete:&lt;/p&gt;

&lt;p&gt;
  &lt;div class=&quot;video-container&quot;&gt;
    &lt;iframe src=&quot;https://www.youtube.com/embed/wzfSDzgP6mA&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
  &lt;/div&gt;
&lt;/p&gt;

&lt;p&gt;Some of our mappers even created &lt;a href=&quot;https://forum.ddnet.org/viewtopic.php?p=1560#p1560&quot;&gt;fancy ingame loading
screens&lt;/a&gt; for the start of
the tournament:&lt;/p&gt;

&lt;p&gt;
  &lt;div class=&quot;video-container&quot;&gt;
    &lt;iframe src=&quot;https://www.youtube.com/embed/_OEgiJFKtQw&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
  &lt;/div&gt;
&lt;/p&gt;

&lt;h2 id=&quot;client--server-development&quot;&gt;Client &amp;amp; Server Development&lt;/h2&gt;

&lt;p&gt;Development of the client and server software is often challenging because of
backwards compatibility:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;People with regular Teeworlds client should still be able to play on DDNet&lt;/li&gt;
  &lt;li&gt;People with old DDNet client (or custom client) should still be able to play&lt;/li&gt;
  &lt;li&gt;Old ranks would be invalidated by slight fixes/changes in game physics&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Nevertheless we managed to put in many new features, for example:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;64 player servers instead of just 16 players:&lt;br /&gt;
Players with vanilla Teeworlds client only see the 15 closest players on a server.&lt;/li&gt;
  &lt;li&gt;Protect players from connection timeouts:&lt;br /&gt;
The timeout protection works by the client having a unique timeout code that
it sends to the server whenever it connects. This tells the server if the
player has had a timeout before and they can get back their ingame character.&lt;/li&gt;
  &lt;li&gt;Allow saving game in team and continuing at a later time:&lt;br /&gt;
The save games are stored in our MariaDB database, but can only be loaded at
a single location. Otherwise players would have the ability to load a save
game multiple times, which is not what we want.&lt;/li&gt;
  &lt;li&gt;Save the ranks of teams and not just single players&lt;/li&gt;
  &lt;li&gt;SDL2 client instead of old SDL1.2&lt;/li&gt;
  &lt;li&gt;New physics: Jetpack, extended teleporters, walljump, tune zones&lt;/li&gt;
  &lt;li&gt;Highly compressed Opus sounds in maps, it’s amazing how much quality you get
with small bit rates&lt;/li&gt;
  &lt;li&gt;In-client updater using HTTPS and a &lt;a href=&quot;http://update2.ddnet.org/update.json&quot;&gt;simple JSON
file&lt;/a&gt; for control&lt;/li&gt;
  &lt;li&gt;Dummy feature to control two ingame characters at once:&lt;br /&gt;
At first this was meant to help map testers to be able to test maps easier,
without requiring a second player. But it’s also very popular with regular
players as well as mappers who come up with interesting maps where only one
player has all the control.&lt;/li&gt;
  &lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Particularly important for the automation of the official DDNet servers is our
FIFO console system. Every server listens for commands from a FIFO file. This
enables us to automate the map releases, server restarts and broadcasts among
other things. To broadcast a message about a new map release to all servers,
you can just do this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;broadcast &quot;New map Uniswim by Im corneum just released on Solo server&quot;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; servers/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.fifo&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We have a &lt;a href=&quot;https://github.com/ddnet/ddnet-scripts/blob/master/servers/bc.py&quot;&gt;small Python
script&lt;/a&gt; that
can do this automatically and also broadcast in a &lt;a href=&quot;https://github.com/ddnet/ddnet-scripts/blob/master/servers/scripts/asciiart&quot;&gt;custom big ASCII art
font&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/public/ascii-ddnet.png&quot; alt=&quot;ASCII DDNET&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Using the FIFO system at multiple locations at once is achieved using Cluster SSH.&lt;/p&gt;

&lt;p&gt;We also offer a list of official DDNet servers &lt;a href=&quot;http://update2.ddnet.org/ddnet-servers.json&quot;&gt;in
JSON&lt;/a&gt; which is used to inform the
client of the official servers as well as internally to generate the &lt;a href=&quot;https://ddnet.org/status/&quot;&gt;status
page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Unfortunately attacks with spoofed IP addresses are quite common for us. So we
have added a spoofing protection using tokens to prevent attackers from filling
up servers with players with spoofed IPs.&lt;/p&gt;

&lt;p&gt;We have no automated tests for the client and server, the community quickly
catches new bugs though. At least we use &lt;a href=&quot;https://circleci.com/gh/ddnet/ddnet&quot;&gt;automated Circle
CI&lt;/a&gt; building to make sure new changes
still compile fine.&lt;/p&gt;

&lt;h2 id=&quot;communication&quot;&gt;Communication&lt;/h2&gt;

&lt;p&gt;Ingame communication is a bit tricky since the game is built on simplicity and
we prefer not to have accounts. Introducing accounts this late into existance
would give another vector of abuse by giving attackers the chance to register
other people’s names. So you can never be sure who you’re actually talking to.
Thanks to some Social Engineering this has been a common source of leaked
moderator passwords.&lt;/p&gt;

&lt;p&gt;So for official communications the &lt;a href=&quot;https://forum.ddnet.org/&quot;&gt;DDNet forum&lt;/a&gt; is
used instead. We tried to use &lt;a href=&quot;https://letsencrypt.org/&quot;&gt;Let’s Encrypt&lt;/a&gt; for
SSL/TLS certificates for the website, but it turned out that Windows XP is &lt;a href=&quot;https://github.com/certbot/certbot/issues/1660&quot;&gt;not
supported&lt;/a&gt; and many of our
players have old systems still running Windows XP. I guess that’s in the nature
of running one of the few actively developed online games that work perfectly
fine on 15 year old machines. Instead we’re now using
&lt;a href=&quot;https://www.startssl.com/&quot;&gt;StartSSL&lt;/a&gt;, which seems to work fine, except that
wildcards would cost money. Once Let’s Encrypt works fine on Windows XP we
might be able to switch back.&lt;/p&gt;

&lt;p&gt;Otherwise technically inclined people mostly discuss on IRC
(&lt;a href=&quot;irc://irc.quakenet.org/ddnet&quot;&gt;#ddnet&lt;/a&gt; on QuakeNet,
&lt;a href=&quot;http://webchat.quakenet.org/?channels=ddnet&amp;amp;uio=d4&quot;&gt;WebChat&lt;/a&gt;,
&lt;a href=&quot;https://ddnet.org/irclogs/&quot;&gt;Logs&lt;/a&gt;) and Skype. Most non-technical discussions
happen via Skype.&lt;/p&gt;

&lt;h2 id=&quot;future-directions&quot;&gt;Future Directions&lt;/h2&gt;

&lt;p&gt;The main goal is to keep DDNet running, well maintained, and keep a steady
stream of new maps from the community.&lt;/p&gt;

&lt;p&gt;We submitted DDNet to &lt;a href=&quot;http://steamcommunity.com/sharedfiles/filedetails/?id=506147661&quot;&gt;Steam
Greenlight&lt;/a&gt;
and have been greenlit since then, which means we could release it on Steam
now. This would be an opportunity for us to reach more potential players,
especially those who have never played Teeworlds before. The main problem right
now is that DDNet is a difficult game to get started with, since it requires
practice, fine control and patience. Right now work is &lt;a href=&quot;https://forum.ddnet.org/viewtopic.php?t=3771&quot;&gt;being
done&lt;/a&gt; to create a tutorial for new
players and generally improve the ease of getting started.&lt;/p&gt;

&lt;h2 id=&quot;discussion&quot;&gt;Discussion&lt;/h2&gt;

&lt;p&gt;You can find discussions about this article on &lt;a href=&quot;https://news.ycombinator.com/item?id=11874830&quot;&gt;Hacker News&lt;/a&gt;, &lt;a href=&quot;https://www.reddit.com/r/programming/comments/4nnlsq/experiences_of_running_an_online_game_for_3_years/&quot;&gt;r/programming&lt;/a&gt; and also the &lt;a href=&quot;https://forum.ddnet.org/viewtopic.php?f=3&amp;amp;t=3779&quot;&gt;DDNet Forum&lt;/a&gt;.&lt;/p&gt;

   </content>
 </entry>
 
 <entry>
   <title>DDNet Server Statistics with ServerStatus, RRDtool and Nim</title>
   <link href="https://hookrace.net/blog/server-statistics/"/>
   <updated>2016-05-14T00:00:00-04:00</updated>
   <id>https://hookrace.net/blog/server-statistics</id>
   <content type="html">
     &lt;p&gt;About a month ago I set up &lt;a href=&quot;https://ddnet.org/stats/server/&quot;&gt;statistics&lt;/a&gt; for the official &lt;a href=&quot;https://ddnet.org/&quot;&gt;DDNet&lt;/a&gt; servers. My motivations for this are:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Monitor the servers more easily&lt;/li&gt;
  &lt;li&gt;Get notified about server problems&lt;/li&gt;
  &lt;li&gt;Have nice graphs to look at&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The choices for the software used are mainly made to keep resource usage low, a
general principle used for DDNet since we run on cheap VPSes all around the
world and are limited in CPU and memory resources. In the rest of this post we
will explore the 3 major tools used, their purpose in our solution as well as
their performance impact:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/BotoX/ServerStatus&quot;&gt;ServerStatus&lt;/a&gt;: Gather live server statistics&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://oss.oetiker.ch/rrdtool/index.en.html&quot;&gt;RRDtool&lt;/a&gt;: Record and graph data&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://nim-lang.org/&quot;&gt;Nim&lt;/a&gt;: Favorite programming language for performance and readability&lt;/li&gt;
&lt;/ol&gt;

&lt;!--more--&gt;
&lt;h2 id=&quot;gathering-live-server-statistics-with-serverstatus&quot;&gt;Gathering live server statistics with ServerStatus&lt;/h2&gt;

&lt;p&gt;We’ve been running BotoX’s &lt;a href=&quot;https://github.com/BotoX/ServerStatus&quot;&gt;ServerStatus&lt;/a&gt; to get &lt;a href=&quot;https://ddnet.org/status/&quot;&gt;live server statistics&lt;/a&gt; for some time now. It works quite well to quickly notice major server problems like a high load or incoming (D)DoS attacks, provided that you keep an eye out for it.&lt;/p&gt;

&lt;p&gt;We use ServerStatus by running its &lt;a href=&quot;https://github.com/BotoX/ServerStatus/blob/master/clients/client.py&quot;&gt;simple Python client&lt;/a&gt; on each server to gather interesting information. The client transmits that data by TCP to the C/C++ server, which aggregates it into &lt;a href=&quot;https://ddnet.org/status/json/stats.json&quot;&gt;a JSON file&lt;/a&gt;. This JSON file is then fetched and displayed every two seconds by the JavaScript frontend of &lt;a href=&quot;https://ddnet.org/status/&quot;&gt;our Status page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On a regular Saturday morning the end result looks like this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/public/ddnet-status.png&quot; alt=&quot;DDNet Status&quot; /&gt;&lt;/p&gt;

&lt;p&gt;On a quick glance you notice that the DDNet.tw server has high CPU usage (which is totally normal since it runs some hefty cron jobs every 20 minutes) and DDNet RUS is receiving a small DoS attack with just 1.6 MB/s (unfortunately also totally normal). Apart from that everything looks fine.&lt;/p&gt;

&lt;p&gt;ServerStatus footprint, calculated from Linux &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/proc&lt;/code&gt; statistics as follows (in Nim):&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-nimrod&quot; data-lang=&quot;nimrod&quot;&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strutils&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;posix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strfmt&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# CPU and memory usage of process, based on PROC(5) and&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# http://stackoverflow.com/a/16736599&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;pid&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;paramStr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;# Clock ticks per second&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;frequency&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sysconf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SC_CLK_TCK&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;# Size of memory page in bytes&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;pagesize&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sysconf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SC_PAGESIZE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;uptime&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;readFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/proc/uptime&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parseFloat&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;fields&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;readFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/proc/&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pid&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/stat&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;split&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;# Amount of time in user mode&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;utime&lt;/span&gt;     &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fields&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;13&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parseInt&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;# Amount of time in kernel mode&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;stime&lt;/span&gt;     &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fields&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parseInt&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;# Amount of children time in user mode&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;cutime&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fields&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;15&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parseInt&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;# Amount of children time in kernel mode&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;cstime&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fields&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parseInt&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;# Time process started after boot&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;starttime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fields&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;21&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parseInt&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;# Resident Set Size: number of pages in memory&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;rssmem&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fields&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;23&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parseInt&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;totaltime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;utime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;stime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cutime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cstime&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;seconds&lt;/span&gt;   &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uptime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;starttime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;frequency&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;cpuusage&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;totaltime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;frequency&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;seconds&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;memusage&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rssmem&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pagesize&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1_000_000&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;interp&quot;${cpuusage:.2f} % CPU ${memusage:.2f} MB Memory&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Part&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;CPU&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Memory&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Client&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.14 %&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;3.70 MB&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Server (9 clients)&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.58 %&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.18 MB&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;recording-and-graphing-data-with-rrdtool&quot;&gt;Recording and graphing data with RRDtool&lt;/h2&gt;

&lt;p&gt;I haven’t used RRDtool for about 7 years, but it’s still an excellent tool to record data into a fixed-size round robin database. For us three functions of RRDtool are important: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;create&lt;/code&gt; to create the database, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update&lt;/code&gt; to add a new value to be aggregated into the database, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;graph&lt;/code&gt; to render the database into a beautiful graph.&lt;/p&gt;

&lt;p&gt;CPU, network and memory are the most important resources for me, so their usage should be recorded. Let us use network traffic as an example and create a database:&lt;/p&gt;

&lt;p&gt;First we need to think about what data we want to record in the RRD:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1 sample = 30 seconds

 1 day  =   2880 samples =   6 * 480 pixels, each pixel is 03:00 min
 7 days =  20160 samples =  42 * 480 pixels, each pixel is 21:00 min
49 days = 141120 samples = 147 * 960 pixels, each pixel is 73:30 min
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then we can use this to create the actual database file:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;rrdtool create ddnet.tw-net.rrd &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# File name` \&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--step&lt;/span&gt; 30 &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Interval in seconds with which data is fed` \&lt;/span&gt;
  DS:network_rx:GAUGE:60:0:U &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Data source receiving` \&lt;/span&gt;
  DS:network_tx:GAUGE:60:0:U &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# DS Sending` \&lt;/span&gt;
  RRA:AVERAGE:0.5:6:480 &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Round robin archive for 1 day` \&lt;/span&gt;
  RRA:AVERAGE:0.5:42:480 &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# RRA for 7 days` \&lt;/span&gt;
  RRA:AVERAGE:0.5:147:960 &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# RRA for 49 days`&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If you’re curious about what exactly happens here, you can find more information in &lt;a href=&quot;https://oss.oetiker.ch/rrdtool/doc/rrdcreate.en.html&quot;&gt;rrdcreate(1)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The resulting ddnet.tw-net.rrd file is just 32 KB in size and will forever stay that exact size. (All our databases together are just 1 MB.) New data in each round robin archive simply overwrites the oldest data. A disadvantage of RRDtool is that you need to think ahead and plan what data you want to store.&lt;/p&gt;

&lt;p&gt;The next step is to put new data into our little database, which we should do every 30 seconds:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;rrdtool update ddnet.tw-net.rrd N:42:1234&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Super simple! &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;42&lt;/code&gt; is our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;network_rx&lt;/code&gt; value, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1234&lt;/code&gt; the value for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;network_tx&lt;/code&gt;. These values are now aggregated using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AVERAGE&lt;/code&gt; and finally put into their respective archives.&lt;/p&gt;

&lt;p&gt;Once we have enough values we can finally create the graph, for example for 1 day:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;rrdtool graph ddnet.tw-net-1d.png &lt;span class=&quot;nt&quot;&gt;--rigid&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--base&lt;/span&gt; 1000 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--width&lt;/span&gt; 419 &lt;span class=&quot;nt&quot;&gt;--height&lt;/span&gt; 150 &lt;span class=&quot;nt&quot;&gt;--logarithmic&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--units&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;si &lt;span class=&quot;nt&quot;&gt;-a&lt;/span&gt; PNG &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Calculation over last day only` \&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--vertical-label&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Bytes/s&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--start&lt;/span&gt; now-1d &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Fetch data from RRD file` \&lt;/span&gt;
  DEF:network_rx&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;ddnet.tw-net.rrd:network_rx:AVERAGE &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  DEF:network_tx&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;ddnet.tw-net.rrd:network_tx:AVERAGE &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Calculate aggregates based on data` \&lt;/span&gt;
  VDEF:network_rx_a&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;network_rx,AVERAGE &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  VDEF:network_rx_m&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;network_rx,MAXIMUM &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  VDEF:network_rx_c&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;network_rx,LAST &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  VDEF:network_rx_s&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;network_rx,TOTAL &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  VDEF:network_tx_a&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;network_tx,AVERAGE &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  VDEF:network_tx_m&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;network_tx,MAXIMUM &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  VDEF:network_tx_c&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;network_tx,LAST &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  VDEF:network_tx_s&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;network_tx,TOTAL &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Draw area graph in light colors` \&lt;/span&gt;
  AREA:network_tx#fee8c8: &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  AREA:network_rx#e0e0e0: &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Draw clear area outline on top` \&lt;/span&gt;
  LINE1:network_tx#e34a33:&lt;span class=&quot;s2&quot;&gt;&quot;out&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Print aggregate values to legend` \&lt;/span&gt;
  GPRINT:network_tx_a:&lt;span class=&quot;s2&quot;&gt;&quot;avg&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; %6.2lf %sB&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  GPRINT:network_tx_m:&lt;span class=&quot;s2&quot;&gt;&quot;max&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; %6.2lf %sB&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  GPRINT:network_tx_c:&lt;span class=&quot;s2&quot;&gt;&quot;cur&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; %6.2lf %sB&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  GPRINT:network_tx_s:&lt;span class=&quot;s2&quot;&gt;&quot;sum&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; %6.2lf %sB&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Other area outline` \&lt;/span&gt;
  LINE1:network_rx#636363:&lt;span class=&quot;s2&quot;&gt;&quot;in &quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  GPRINT:network_rx_a:&lt;span class=&quot;s2&quot;&gt;&quot;avg&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; %6.2lf %sB&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  GPRINT:network_rx_m:&lt;span class=&quot;s2&quot;&gt;&quot;max&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; %6.2lf %sB&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  GPRINT:network_rx_c:&lt;span class=&quot;s2&quot;&gt;&quot;cur&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; %6.2lf %sB&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  GPRINT:network_rx_s:&lt;span class=&quot;s2&quot;&gt;&quot;sum&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; %6.2lf %sB&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;As always, the &lt;a href=&quot;http://oss.oetiker.ch/rrdtool/doc/rrdgraph.en.html&quot;&gt;manual of rrdgraph&lt;/a&gt; explains the possibilities.&lt;/p&gt;

&lt;p&gt;I’m using RRDtool 1.6.0 instead of 1.4.8 because I very much prefer its density of x-axis labels. Here are the outputs of our database:&lt;/p&gt;

&lt;p&gt;RRDtool 1.4.8: &lt;img src=&quot;/public/ger.ddnet.tw-net-1d-1.4.8.png&quot; alt=&quot;RRDtool 1.4.8&quot; /&gt;
RRDtool 1.6.0: &lt;img src=&quot;/public/ger.ddnet.tw-net-1d-1.6.0.png&quot; alt=&quot;RRDtool 1.6.0&quot; /&gt;&lt;/p&gt;

&lt;p&gt;RRDtool footprint:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Part&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Runtime&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Memory&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Create (9 servers)&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.01 s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1.74 MB&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Graph  (9 servers)&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1.99 s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;2.82 MB&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;putting-it-together-with-nim&quot;&gt;Putting it together with Nim&lt;/h2&gt;

&lt;p&gt;To aggregate the raw data from ServerStatus into 30-second packets I use a small Nim program. It automatically creates new databases when a new server is added and keeps them updated:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-nimrod&quot; data-lang=&quot;nimrod&quot;&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;common&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;osproc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;times&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strutils&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tables&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;Data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;object&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;network_rx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;network_tx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BiggestInt&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;cpu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;memory_used&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;memory_total&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;swap_used&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;swap_total&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BiggestInt&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;freq&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# report new data to rrd every 30 seconds&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;lastUpdated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BiggestInt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;dataTable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;initTable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;countTable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;initTable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;proc &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rrdCreate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dataSources&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;discard&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;execCmd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rrdtool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; create &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;file&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; --step &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;freq&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dataSources&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot; RRA:AVERAGE:0.5:6:480 RRA:AVERAGE:0.5:42:480 RRA:AVERAGE:0.5:147:960&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;proc &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rrdUpdate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;varargs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;valuesString&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;valuesString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;len&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;valuesString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;:&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;valuesString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;discard&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;execCmd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rrdtool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; update &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;file&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; N:&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;valuesString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;proc &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;updateServer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;JsonNode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;str&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dataTable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mgetOrPut&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fieldPairs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;BiggestInt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;num&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fnum&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Unhandled type in Data object&quot;&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;inc&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;countTable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mgetOrPut&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;# Only save data if we got 30 values in the expected time span&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;freq&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;countTable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;freq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dataTable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;dataTable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;del&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;dataTable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;fileNet&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rrdDir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-net.rrd&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;fileCpu&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rrdDir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-cpu.rrd&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;fileMem&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rrdDir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-mem.rrd&quot;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;existsFile&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fileNet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;fileNet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rrdCreate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;DS:network_rx:GAUGE:60:0:U DS:network_tx:GAUGE:60:0:U&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;existsFile&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fileCpu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;fileCpu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rrdCreate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;DS:cpu:GAUGE:60:0:100 DS:load:GAUGE:60:0:U&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;existsFile&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fileMem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;filemem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rrdCreate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;DS:memory_used:GAUGE:60:0:U DS:memory_total:GAUGE:60:0:U DS:swap_used:GAUGE:60:0:U DS:swap_total:GAUGE:60:0:U&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;fileNet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rrdUpdate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;network_rx&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;freq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;network_tx&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;freq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fileCpu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rrdUpdate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cpu&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;freq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;load&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;freq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fileMem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rrdUpdate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;memory_used&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;freq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;memory_total&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;freq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;swap_used&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;freq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;swap_total&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;div&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;freq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;proc &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;updateAllServers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;statsJson&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parseFile&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;statsJsonFile&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newUpdated&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parseBiggestInt&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;statsJson&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;updated&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;str&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newUpdated&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lastUpdated&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;inc&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;statsJson&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;servers&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;updateServer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;discard&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;freq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;countTable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mvalues&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;startTime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;epochTime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;updateAllServers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;epochTime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;startTime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# every second&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The final graphs can be seen on the &lt;a href=&quot;https://ddnet.org/stats/server/&quot;&gt;DDNet Server Statistics page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Nim monitor footprint:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Part&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;CPU&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Memory&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Monitor (9 servers)&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.03 %&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.89 MB&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;alerts-through-cron-and-mail&quot;&gt;Alerts through Cron and Mail&lt;/h2&gt;

&lt;p&gt;Now we certainly have nice graphs, but automated alerts about suspicious events would be even better, for example:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Network traffic over 2 MB/s for 4 min&lt;/li&gt;
  &lt;li&gt;Memory and swap over 90% for 4 min&lt;/li&gt;
  &lt;li&gt;CPU over 90% for 21 min&lt;/li&gt;
  &lt;li&gt;Load over 10 for 21 min&lt;/li&gt;
  &lt;li&gt;Server unreachable for 1 hour&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To check these conditions a cron job is run regularly and thanks to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MAILTO&lt;/code&gt; entry a mail is sent when an alert has been triggered.&lt;/p&gt;

&lt;p&gt;We can get out a single value from the database to standard output using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PRINT&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;rrdtool graph x &lt;span class=&quot;nt&quot;&gt;-s-4min&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  DEF:v&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;ddnet.tw-net.rrd:network_rx:AVERAGE &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  VDEF:vm&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;v,AVERAGE &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  PRINT:vm:%lf&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The alert program itself is written in Nim as well and merely gets a few values from the databases and checks if the limits are exceeded:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-nimrod&quot; data-lang=&quot;nimrod&quot;&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;common&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;osproc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strutils&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;proc &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;errorCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;execCmdEx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rrdtool&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; graph x -s -&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; DEF:v=&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;file&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;:&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;:AVERAGE VDEF:vm=v,AVERAGE PRINT:vm:%lf&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;errorCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;raise&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ValueError&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Error code &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;errorCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; from rrdtool: &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;splitLines&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;parseFloat&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;paramCount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;alert [1d|7d|49d]&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;quit&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;statsJson&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;parseFile&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;statsJsonFile&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;statsJson&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;servers&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;str&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;str&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fileNet&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rrdDir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-net.rrd&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fileCpu&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rrdDir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-cpu.rrd&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fileMem&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rrdDir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-mem.rrd&quot;&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;template&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;paramStr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1d&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fileNet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;network_rx&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;4min&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fileNet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;network_tx&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;4min&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2_000_000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;alert&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;network traffic over 2 MB/s for 4 min&quot;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fileMem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;memory_used&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;4min&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fileMem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;swap_used&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;4min&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.9&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fileMem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;memory_total&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;4min&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fileMem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;swap_total&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;4min&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)):&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;alert&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;memory and swap over 90% for 4 min&quot;&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;7d&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fileCpu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;cpu&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;21min&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;90.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;alert&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;CPU over 90% for 21 min&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fileCpu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;load&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;21min&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;10.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;alert&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Load over 10 for 21 min&quot;&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;49d&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;network_rx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fileNet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;network_rx&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;4410&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;network_rx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;network_rx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# NaN&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;alert&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;unreachable for 1 hour&quot;&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;unknown parameter &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;paramStr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;quit&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Nim alert footprint:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Part&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Runtime&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Memory&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Alert (9 servers)&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.25 s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;2.12 MB&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Live view of the last day of the DDNet.tw web server, also hosting this blog:
&lt;img src=&quot;https://ddnet.org/stats/server/ddnet.tw-net-1d.png&quot; alt=&quot;DDNet.tw Net&quot; /&gt;
&lt;img src=&quot;https://ddnet.org/stats/server/ddnet.tw-cpu-1d.png&quot; alt=&quot;DDNet.tw CPU&quot; /&gt;
&lt;img src=&quot;https://ddnet.org/stats/server/ddnet.tw-mem-1d.png&quot; alt=&quot;DDNet.tw Mem&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can see the full graphs on the &lt;a href=&quot;https://ddnet.org/stats/server/&quot;&gt;DDNet Server Statistics page&lt;/a&gt;. As usual you can find the entire source code in our &lt;a href=&quot;https://github.com/ddnet/ddnet-scripts/tree/master/rrd&quot;&gt;git repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;All in all the system runs on very little resources, puts out some nice graphs and alerts me automatically about defined problems.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Part&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Runtime&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;CPU&lt;/th&gt;
      &lt;th style=&quot;text-align: right&quot;&gt;Memory&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Client&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.14 %&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;3.70 MB&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Server (9 clients)&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.58 %&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.18 MB&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Create (9 servers)&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.01 s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1.74 MB&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Graph  (9 servers)&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;1.99 s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;2.82 MB&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Monitor (9 servers)&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.03 %&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.89 MB&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Alert (9 servers)&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;0.25 s&lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt; &lt;/td&gt;
      &lt;td style=&quot;text-align: right&quot;&gt;2.12 MB&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

   </content>
 </entry>
 
 <entry>
   <title>DDNet Live: Twitch spectates an online game</title>
   <link href="https://hookrace.net/blog/ddnet-live/"/>
   <updated>2016-01-10T00:00:00-05:00</updated>
   <id>https://hookrace.net/blog/ddnet-live</id>
   <content type="html">
     &lt;p&gt;Last night I had an idea and implemented it, soo let’s see what will happen.
But first, the idea:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Have a livestream [1] of &lt;a href=&quot;https://ddnet.org/&quot;&gt;DDNet&lt;/a&gt; running non-stop [2] that
always shows some interesting [3] players on the server.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The resulting livestream is running on &lt;a href=&quot;https://www.twitch.tv/ddnetlive&quot;&gt;Twitch&lt;/a&gt;.
All the scripts are on &lt;a href=&quot;https://github.com/ddnet/ddnet-live&quot;&gt;Github&lt;/a&gt;&lt;/p&gt;

&lt;!--more--&gt;
&lt;h2 id=&quot;1-livestream&quot;&gt;1. Livestream&lt;/h2&gt;

&lt;p&gt;It’s surprisingly simple to livestream from Linux to Twitch. Only FFmpeg is needed:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/sh&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;INRES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;1280x720&quot;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# input resolution&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;FPS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;30&quot;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# target FPS&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;GOP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;60&quot;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# i-frame interval, should be double of FPS, &lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;GOPMIN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;30&quot;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# min i-frame interval, should be equal to fps, &lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;THREADS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;CBR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2000k&quot;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# constant bitrate (should be between 1000k - 3000k)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;QUALITY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ultrafast&quot;&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# one of the many FFMPEG preset&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;AUDIO_SRATE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;44100&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;AUDIO_CHANNELS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2&quot;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# 1 for mono output, 2 for stereo&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;AUDIO_ERATE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;96k&quot;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# audio encoding rate&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;SERVER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;live-fra&quot;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;#  http://bashtech.net/twitch/ingest.php for list&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;source&lt;/span&gt; ./secret.sh

ffmpeg &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; 0 &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; x11grab &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$INRES&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$FPS&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; :0.0 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; alsa &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; pulse &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; flv &lt;span class=&quot;nt&quot;&gt;-ac&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$AUDIO_CHANNELS&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-b&lt;/span&gt;:a &lt;span class=&quot;nv&quot;&gt;$AUDIO_ERATE&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-ar&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$AUDIO_SRATE&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-vcodec&lt;/span&gt; libx264 &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$GOP&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-keyint_min&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$GOPMIN&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-b&lt;/span&gt;:v &lt;span class=&quot;nv&quot;&gt;$CBR&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-minrate&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$CBR&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-maxrate&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$CBR&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-vf&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;format=yuv420p&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-preset&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$QUALITY&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-acodec&lt;/span&gt; libmp3lame &lt;span class=&quot;nt&quot;&gt;-threads&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$THREADS&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-strict&lt;/span&gt; normal &lt;span class=&quot;nt&quot;&gt;-bufsize&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$CBR&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;rtmp://&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$SERVER&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.twitch.tv/app/&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$STREAM_KEY&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This works pretty well, but takes quite a bit of CPU on my old computer. So
instead I wanted to run it on my new server with a Haswell-era J1900 CPU.&lt;/p&gt;

&lt;p&gt;Possible ways to improve performance:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Use an OpenGL recorder instead of x11grab&lt;/li&gt;
  &lt;li&gt;Get Intel Quick Sync Video working for hardware H264 encoding&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;2-running-non-stop&quot;&gt;2. Running Non-Stop&lt;/h2&gt;

&lt;p&gt;My server is a small and cheap ASRock Q1900-ITX:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/public/q1900-itx.jpg&quot; alt=&quot;Q1900-ITX&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The nice part is that it’s passively cooled and quite power efficient, drawing
only 5 Watts in idle. This machine has been running as my home server for quite
some time, but barely gets any action. Let’s change that!&lt;/p&gt;

&lt;p&gt;The X server starts without problems even without any monitors attached, the
only thing that’s left to do is increasing the framebuffer size so that our
game can run in it:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat &lt;/span&gt;ddnet.sh
&lt;span class=&quot;c&quot;&gt;#!/bin/sh&lt;/span&gt;

pulseaudio &lt;span class=&quot;nt&quot;&gt;--start&lt;/span&gt;
xrandr &lt;span class=&quot;nt&quot;&gt;--fb&lt;/span&gt; 1280x720 &lt;span class=&quot;c&quot;&gt;# Adjust framebuffer&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cp &lt;/span&gt;settings_ddnet.cfg ~/.teeworlds/ &lt;span class=&quot;c&quot;&gt;# Restore backup&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;ddnet &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./DDNet

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;xinit ddnet.sh&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We don’t even need a window manager. After all we just run a single window in
exactly the resolution of the framebuffer.&lt;/p&gt;

&lt;p&gt;The FFmpeg recording still works in exactly the same way. Unfortunately I
couldn’t get H264 hardware encoding to work with the J1900 CPU. &lt;a href=&quot;https://github.com/shenhailuanma/qsv-ffmpeg-codec/issues/3&quot;&gt;Related bug
reports&lt;/a&gt; make me
believe it just doesn’t work on these cheaper Intel CPUs.&lt;/p&gt;

&lt;h2 id=&quot;3-artificial-intelligence-twitch-control&quot;&gt;3. &lt;s&gt;Artificial Intelligence&lt;/s&gt; Twitch Control&lt;/h2&gt;

&lt;p&gt;Now that we have the game running and are streaming it to Twitch, we need to
control it somehow. My goal was to find an approach that always shows some
interesting players in action, so that you could watch the stream all day and
enjoy it.&lt;/p&gt;

&lt;p&gt;But finding a reasonable way to do this seems too complicated and I didn’t look
forward to hacking the DDNet C/C++ source code, so instead I opted to utilize
an existing system in DDNet: FIFO command input!&lt;/p&gt;

&lt;p&gt;DDNet servers and clients can be remote controlled through a FIFO file. This is
very useful to send the same commands to dozens of servers at once. But for the
client its use was pretty limited, until now!&lt;/p&gt;

&lt;p&gt;Instead of thinking of an algorithm to find interesting players, why not let
the Twitch viewers themselves control who they want to watch through the chat?&lt;/p&gt;

&lt;p&gt;I did not modify the DDNet source code in any way and instead wrote a small
&lt;a href=&quot;http://nim-lang.org/&quot;&gt;Nim&lt;/a&gt; script based on the &lt;a href=&quot;https://github.com/nim-lang/irc&quot;&gt;IRC
module&lt;/a&gt; to connect to Twitch’s IRC server and
forward the commands to the FIFO:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-nimrod&quot; data-lang=&quot;nimrod&quot;&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;irc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;strutils&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;secret&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;forbiddenCommands&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;exec&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;quit&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;exit&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;disconnect&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newIrc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;irc.twitch.tv&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nick&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;ddnetlive&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;serverPass&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serverPass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;joinChans&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#ddnetlive&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;fifo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;input.fifo&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fmWrite&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;proc &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nick&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cmd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;forbiddenCommands&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cmd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;contains&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nick&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;: &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cmd&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;stdout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;flushFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;fifo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cmd&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;fifo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;fifo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;flushFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TIrcEvent&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;poll&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;typ&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EvConnected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;discard&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EvDisconnected&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EvTimeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reconnect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EvMsg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cmd&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MPrivMsg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nick&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;discard&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Right now there is really not much limitation to what you can do. Only a
handful of commands are explicitly blocked, otherwise the client can be
controlled freely and every single chat message is sent to the client. The
&lt;a href=&quot;https://ddnet.org/settingscommands/#client-commands&quot;&gt;client commands&lt;/a&gt; and
&lt;a href=&quot;https://ddnet.org/settingscommands/#client-settings&quot;&gt;client settings&lt;/a&gt; list the
available commands and settings. &lt;a href=&quot;https://ddnet.org/settingscommands/#chat-commands&quot;&gt;Chat
commands&lt;/a&gt; can be sent to the
server as well through the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;say&lt;/code&gt; command. Some examples:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;connect ger.ddnet.org:8303
team &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Join spectators&lt;/span&gt;
spectate_next &lt;span class=&quot;c&quot;&gt;# Spectate the next player&lt;/span&gt;
spectate 0 &lt;span class=&quot;c&quot;&gt;# Spectate player with ID 0&lt;/span&gt;
say Hi from twitch.tv/ddnetlive &lt;span class=&quot;c&quot;&gt;# Write chat messages&lt;/span&gt;
player_name DDNetLive &lt;span class=&quot;c&quot;&gt;# Change name&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Let’s see how this goes. Luckily the Nim script runs independently from the
server, so I will be able to make changes to it on the fly. Just be nice and
don’t cause any trouble, thanks.&lt;/p&gt;

&lt;p&gt;Feel free to &lt;a href=&quot;https://twitch.tv/ddnetlive&quot;&gt;head over to Twitch&lt;/a&gt;, watch the
action and control the server using the Twitch chat. Twitch has some delay, so
it takes a few seconds for you to see your command executed.&lt;/p&gt;

   </content>
 </entry>
 
</feed>
