Videotutorials für League of Legends
Die LoL-Tipps von mir sind bestenfalls Grundlagen. Viel tiefgehender und in Videoform sowieso nützlicher sind die folgenden Youtube-Tutorials, über die ich in den letzten Tagen gestolpert bin.
Spielanalyse
Ursprünglich ein Low-Elo-Experiment, hat der Spieler WeltraumPennner mehrere seiner Spiele kommentiert, analysiert und hochgeladen. Das sind keine Spiele auf dem höchsten Niveau (plus vor der Einführung des Honor-Systems, also voller Flame-Kiddies im Chat), aber sehr hilfreiche und reflektierte Analysen der Fehler seines Teams und von ihm selbst.
Tutorial für AD-Carries
Mit normalem Schaden das eigene Team zum Sieg zu tragen ist nicht so ohne. Eine fünfteilige Reihe von awesomefusion behandelt richtiges Positionieren, Kämpfen, Wählen, Lanen und den Item-Build. Sehr hilfreich.
Tutorial für AP-Mid
Ähnlich aufgebaut wie der AD-Guide, gibt es das gleiche von einem anderen Spieler für die Mid-Lane. Bisschen schwerer zu verstehen, bisschen schlechter strukturiert, aber auch nicht verkehrt.
Cache für Sinatra
Ich habe eine ziemlich funktionsfähige Blogsoftware mit Sinatra gebaut. Ein absolutes Pflichtfeature war ein Cache, also dass einmal generierte Seiten irgendwo gespeichert und nachher ohne Neuerstellung genutzt werden können. Caches bei Blogsoftware sind unumgänglich, werden Blogs doch viel öfter gelesen als beschrieben. In meinem Fall ist nicht memcached oder ähnliches der dafür geeignete Ort, sondern die auch für alles andere genutzte SQLite-Datenbank.
Grundlage
Ein Cache ist im einfachsten Fall ein einfacher Key-Value-Speicher. Soll er automatisch nicht unbegrenzt speichern, braucht er irgendein Mittel zur automatischen Cache-Invalidierung, also wird noch ein Time-To-Live-Feld (ttl) eingebaut, das automatisch befüllt wird. Insgesamt:
@db.execute "CREATE TABLE IF NOT EXISTS cache( key TEXT PRIMARY KEY, value TEXT, ttl INTEGER DEFAULT (strftime('%s','now') + 604800) );"
Die Funktionen zum Befüllen und Holen von Werten:
def cache(key, value) begin @db.execute("INSERT OR IGNORE INTO cache(key, value) VALUES(?, ?)", key, value) @db.execute("UPDATE cache SET value = ?, ttl = (strftime('%s','now') + 604800) WHERE key = ?", value, key) rescue => error puts error end end def getCache(key) begin return @db.execute("SELECT value FROM cache WHERE key = ? AND ttl > strftime('%s','now') LIMIT 1;", key)[0]['value'] rescue => error puts error end end
In der restlichen Sinatraanwendung kann man nun alle GET-Requests durch den Cache laufen lassen: Nur wenn der keinen Inhalt hat wird der eigentliche Code ausgeführt und dessen Ergebnis dann gecached. Dafür ideal geeignet sind die before() und after() Funktionen von Sinatra, die vor und nach jedem Seitenaufruf ausgeführt werden.
Vor jedem Aufruf wird der Cache geprüft:
before do @cacheContent = Database.new.getCache(request.path_info) end after do Database.new.cache("request.path_info", body) end
In den eigentlichen Funktionen muss nun vor der eigentlichen Codeausführung auf den Cache reagiert werden:
get '/' do if @cacheContent != nil return @cacheContent end ... end
Und, wichtig: Damit body während after() schon gesetzt ist, muss er explizit gesetzt werden:
get '/' do if @cacheContent != nil return @cacheContent end ... # do stuff body erb :index # instead of erb :index end
Schon steht ein Cache.
Optimierungen
So wie oben reicht das natürlich noch nicht. Zum einen darf natürlich nicht jeder Request gecached werden, sondern nur GET-Requests:
before do @cacheContent = nil if request.request_method == "GET" @cacheContent = Database.new.getCache(request.path_info) end end after do if @cacheContent == nil && request.request_method == "GET" Database.new.cache(request.path_info, body) end end
Außerdem soll der Cache invalidiert werden, wenn mit einem POST-Request schreibend der Bloginhalt verändert wurde, sei es durch einen Kommentar oder einen neuen Artikel. Je granularer man hier arbeitet, desto besser wird der Cache funktionieren, aber im Zweifel muss eben der ganze Cache invalidiert werden. Entweder man führt dazu einen Cache-Instanzvariable ein, die dann verändert wird, sodass der Cacheinhalt nicht mehr gefunden wird. Oder man löscht einfach den gesamten Cacheinhalt:
def invalidateCache() begin return @db.execute("DELETE FROM cache WHERE key LIKE '/%'") rescue => error puts error end end
Da der Cache in meinem Fall noch andere Elemente beinhaltet, wird hier das Löschen über /% auf Elemente mit führendem /, wie eben alle request.path_info-Keys aussehen, eingeschränkt.
Auf der Sinatra-Ebene wird das bei jedem POST ausgelöst (alternativ sicherer auf Datenbankebene bei jedem relevanten Write):
after do if @cacheContent == nil && request.request_method == "GET" Database.new.cache(request.path_info, body) else if request.request_method == "POST" Database.new.invalidateCache end end end
Ganz reicht das noch nicht. In meinem Blog gibt es zwei Nutzergruppen, die unterschiedliche Seiten präsentiert bekommen: Einfache Besucher und solche mit Schreibrechten, Admins. Derzeit würden noch beide die gleichen Seiten gecached bekommen, je nachdem, welche Gruppe die Seite zuerst aufruft. Um da zu unterscheiden wird am einfachsten der key angepasst:
before do @cacheContent = nil if request.request_method == "GET" @cacheContent = Database.new.getCache("#{request.path_info}#{isAdmin?}") end end after do if @cacheContent == nil && request.request_method == "GET" Database.new.cache("#{request.path_info}#{isAdmin?}", body) else if request.request_method == "POST" Database.new.invalidateCache end end end
Das ist dann auch die derzeit finale Version.
Performance
Bringt das was? Benchmarken soll man zwar nie auf dem PC, auf dem der Server läuft, hier aber geht es nur um den Unterschied, daher halte ich das für legitim.
ab -n 1000 -c 5 http://localhost:4567:
Ohne Cache:
Requests per second: 23.58 [#/sec] (mean) Time per request: 212.042 [ms] (mean) Time per request: 42.408 [ms] (mean, across all concurrent requests) Transfer rate: 131.44 [Kbytes/sec] received
Mit Cache:
Requests per second: 124.77 [#/sec] (mean) Time per request: 40.074 [ms] (mean) Time per request: 8.015 [ms] (mean, across all concurrent requests) Transfer rate: 695.50 [Kbytes/sec] received
Grafisch:
Bei einem simulierten Benutzeransturm hilft der Cache definitiv (und mit 500% weit mehr als ich erhofft hatte).
Ohne Last sinkt die Wartezeit auf den Server, bei gefülltem Cache und laut Chrome, von 54ms auf 27ms. Auch nicht schlecht.
Lamport-Diffie Einmal-Signaturverfahren
Willkommen in der Zukunft. Durch Quantencomputer ist Primfaktorzerlegung kein Problem mehr, RSA und andere Verfahren sind nutzlos geworden. Das unheimlich wichtige Dokument D=(1,1,0) soll aber trotzdem signiert werden, also braucht es dafür ein anderes Verfahren mit einer anderen Sicherheitsbedingung. Das Lamport-Diffie Einmail-Signaturverfahren (LD-OTS) eilt zur Rettung.
LD-OTS braucht nur eine funktionierende Hashfunktion, dass eine solche noch existiert ist Mindestvoraussetzung. Und dann ist es ganz einfach, der Signierer bietet schlicht die passenden Urbilder zu den vorher veröffentlichten und durchs Dokument ausgewählten Hashs an.
Also so: Für jedes Bit im Dokument hat der Signierer zwei Werte, die geheim bleiben, den Signaturschlüssel, hier: x = (x01, x11; x02, x12; x03, x13).
Veröffentlicht wird vorher der Verifizierungsschlüssel: y = (y01, y11; y02, y12; y03, y13) = (H(x01), H(x11); H(x02), H(x12); H(x03), H(x13))
Zum Signieren des Dokuments D = (1, 1, 0) veröffentlicht der Signierer dann je nach Bit an der jeweiligen Stelle seinen Teil des Signaturschlüssels und damit die Urbilder der Hashfunktion als Signatur, hier also: (x11, x12, x03). Der Verifizierer braucht dann dann nur zu schauen, ob die veröffentlichten Signaturschlüsselteile mit dem vorher veröffentlichten Verifizierungsschlüssel übereinstimmen, also H(x11) = y11, H(x12)=y12 und H(x03)=y03 ist.
Bilder für Dropbox Camera Upload
Um den Speicherplatz von Dropbox zu erweitern ist die Nutzung des Bilder-Imports für Smartphones eine Möglichkeit. Immer wenn ein Block von 500 MB Bilder angefangen wird bekommt man diese 500 als Bonus-Speicherplatz dazu, bis zu einer Grenze von 3GB (also 2,501GB an Bilder = 3GB mehr Speicherplatz). Ich muss das noch testen, aber angeblich bleibt das auch nach Löschen der Bilder erhalten.
Leider funktioniert das nicht unter Linux und so viele Bilder muss man auch erstmal auf dem Handy haben. Ersteres kann man wohl derzeit nicht lösen, also muss Windows gebootet werden, aber woher die Bilder nehmen? 2500mal auf den Auslöser drücken? Einfacher wäre es ja, die Bilder herunterzuladen und auf dem Handy zu speichern. Dropbox erkennt zwar hervorragend Duplikate (deswegen kann man meinem Test nach nicht einfach ein Bild nehmen und das entsprechend häufig kopieren), aber aus dem Internet heruntergeladene Bilder statt selbst aufgenommenen werden ja hoffentlich nicht als solche erkannt und sollten für das Limit zählen.
Doch beliebig Bilder herunterzuladen ist auch gar nicht so einfach. Software und Webseiten zum Ziehen von Bildern aus der Google-Bildersuche kranken entweder an dem 64-Bilder-Limit oder sind furchtbar langsam. Dann fiel mir ein, dass ich eine passende Software für ein sehr ähnliches Szenario mal mit einem Team gebaut habe: image-sacon, ein Tool zum massenhaften Herunterladen von Bildern von Imagehostern bzw Flickr. Tatsächlich funktioniert die Software, obwohl nicht hübsch und seit dem Ende des Uniprojekts nicht mehr gepflegt, bei mir gerade einwandfrei.
Hab ich schonmal vorgestellt, aber selbst fast völlig vergessen.
Damit werde ich Bilder herunterladen, schiebe die aufs Handy, starte Windows und lasse sie von Dropbox importieren, schließlich hochladen und habe dadurch nach ihrer Löschung hoffentlich 3GB mehr Speicher.
Zusammen mit dem Space-Race ist das dann ein bisschen besser als die 2GB am Anfang.
Speicherverbrauch und Startzeit unter Precise
Diesmal sind es nicht meine Daten. Dieser Artikel über die Startzeiten und Speicherverbrauch von grafischen Oberflächen (via) unter Ubuntu 12.04 in Charts:
Im Grunde nichts überraschendes: KDE ist wie eh und je träge und groß (im Betrieb sieht das anders aus), Unity steht dem nicht viel nach. Nur dass E17 mit twm nicht im mindesten mithalten kann habe auch ich nicht erwartet. E17 ist inzwischen wirklich als Desktopumgebung einzuordnen.
Doppelte Seiten in s9y
Diese Tipps (alternativ im Forum) finde ich gar nicht so sinnlos. Auch wenn seo im Titel steht.
Bei dem Vermeiden von doppelten Seiten muss es nicht um Suchmaschinenoptimierung gehen. Bei mir bemerkte ich z.B. erstmal gar keinen positiven Effekt in der Hinsicht, sondern schlicht weniger indexierte Seiten. Aber oftmals sind diese doppelten Seiten nicht stabil, wenn ein Besucher auf einer Suchmaschine also einem solchen Link folgt findet er wahrscheinlich nicht was er sucht. Auch wenn mein Patch für die stabilen Seiten das zumindest für das Archiv verhindert.
In Kurzform die Vorschläge:
{if ($view == "entry" || $view == 'start' || $staticpage_pagetitle != '')} <meta name="robots" content="index,follow" /> {else} <meta name="robots" content="noindex,follow" /> {/if}
in die index.tpl, damit nur die Startseite, statische Seiten und einzelne Artikel indexiert, der Rest aber noch gecrawlt wird. Im Folgeartikel dann noch der sinnvolle Vorschlag, über
{if ($view == "entry")} <link rel="canonical" href="{$entry.rdf_ident}" /> {/if} {if ($view == "start")} <link rel="canonical" href="{$serendipityBaseURL}" /> {/if}
eine kanonische URL zu setzen.
Desweiteren werden die /comments- und /authors-Seiten kritisiert, eigentlich ganz praktische Übersichtsseiten. Die Kommentarübersicht ist nirgends verlinkt und war mir gar nicht bekannt, ich bezweifel, dass die ein Problem ist. Bei /authors sieht das anders aus, bei einem Einzelnutzerblog ist die eine völlig unnötige Spiegelung des gesamten Blogs. Mit der .htaccess werde ich nicht rumspielen, aber in der entries.tpl statt /authors eine eigens erstellte Autorenseite zu verlinken erschien mir sinnvoll.
Stabiler RSS-Feed von Roche & Böhmermann
Wer einen stabilen Feed der Talkshow Roche & Böhmermann abonnieren will, kann diese Pipe (oder direkt diesen Feed) abonnieren.
Der Originalfeed wirft immer wieder alte Einträge nach oben.
Edit: Wäre zu schön gewesen, der Originalfeed ist so kaputt das die Pipe versagt.
Caching für s9y
Der letzte Artikel hat scheinbar einige Klicks abbekommen. Ich habe keine Statistik über die Besucherzahlen, aber man merkte es an der Last: Die stieg von normalerweise 0,2 auf 1,3. Das ist an sich kein hoher Wert, aber man bemerkte es hier leider durch deutlich längere Ladezeiten.
Deswegen habe ich das cachesimple-Plugin aktiviert und es war faszinierend zu sehen, wie sofort die Last absank und die Ladezeiten auf normale Werte zurückfielen, sogar eher kürzer wurden. Das Plugin zu aktivieren ist also auf jeden Fall eine gute Idee, wenn der Blog ab und an ein paar Aufrufe mehr stemmen muss, wobei der Cache dynamische Features wie das Karma- oder Statistik-Plugin stört. Das macht ein Server-Zusammenbruch unter Last allerdings auch.
Nur sporadisch stieg die Last nochmal an, das legte sich aber immer wieder schnell und könnte am Plugin gelegen haben. Sicherheitshalber wurde noch das Echtzeitkommentar-Plugin entfernt, das bei vielen offenen Artikelseiten ordentlich Serverlast verursachen kann.
Da das cachesimple-Plugin aber schon ewig als experimentell gekennzeichnet ist habe ich mal nachgefragt, was der derzeitige Stand ist. Ein Blog sollte ja eigentlich immer komplett gecached sein, da er viel öfter gelesen als beschrieben wird, auch mit Kommentaren. Und eigentlich will man eine Struktur, die zum einen alle Datenbankabfragen cached, nahezu alle PHP-Logik drumherum und dann am besten nochmal den Zusammenbau der einzelnen Seitenbestandteile (hier Smarty), um schließlich trotzdem noch dynamische Plugins zu erlauben. Ich hoffe, die Diskussion geht weiter und es kommt Code dabei herum - auch um das morgen noch im Kopf zu haben schreibe ich das hier gerade - aber Caching auf ein bestehendes und dafür nicht unbedingt passend entworfenes System wie s9y draufzusetzen ist gar nicht so einfach, soviel scheint klar.
Die Powercache-Variante von Serendipity, über die wir da reden, ist übrigens auf github zu finden, und die Idee von Idee von Kris Köhntopp meint diesen Artikel.
Ubuntu fordert beim Download zum Spenden auf
Und trickst dabei.
Nachdem die Werbung im Unity-Dash doch einige Reaktionen ausgelöst hat, versucht Canoncial sich nun an einer vermeintlich harmloseren Einnahmequelle, die in der Diskussion auch desöfteren vorgeschlagen wurde: Einem einfachen Weg zu spenden. Dass ich das wieder nicht gelungen finde zeigt, in was für einer schweren Position Canonical ist.
Der Spendenaufruf ist nicht einfach prominent auf der Homepage untergebracht oder in Ubuntu selbst integriert, sondern in den Download-Ablauf integriert. Ich zeige das mal:
Zwischengeschaltet ist also diese Paypal-Spendenseite, deren Betrag (aber nur über die einzelnen Slider) selbst gewählt werden kann. Und diese Seite muss kritisiert werden. Denn naturgemäß ist sie darauf ausgelegt, den Besucher zum Spenden zu verleiten, aber bedient sich dabei einiger Tricks, die im Ubuntu-Umfeld meiner Meinung nach unangemessen sind.
-
Der Text dieser Seite wirkt erstmal völlig ok. "Pay what you think it's worth" enthält die Möglichkeit, dass man nicht bezahlen muss. Aber es macht es nicht völlig klar, es ist kein "Want to pay us something?". Die Intention der Headline ist "zahle etwas und variiere die Höhe" und nimmt damit den ersten Schritt "zahle überhaupt etwas" vorweg, der bei freier und kostenloser Software eigentlich der wichtigste ist.
-
Dann die beiden Buttons unten. Wobei, eigentlich sind es eben nicht zwei. Nur der Paypal-Link ist als Button gestaltet und ist noch dazu auf der üblichen Position zum Vorwärts-Klicken eines Dialogs, rechts unten. Es ist aus beiden Gründen, Position wie Gestaltung, für jeden Nutzer viel natürlicher, den Paypal-Button zu drücken und damit zu spenden. Der "Not now, take me to the download"-Link ist dagegen wesentlich weniger prominent als Link gestaltet und nicht als Button, ist auf der Position des "Zurück" oder "Abbrechen" links unten. Und er ist nicht umsonst mit einem so langen Linktext versehen. Jeden Nutzer wird es bewusste Aufmerksamkeit kosten, diesen Link statt des Paypal-Buttons zu drücken.
-
Dazu kommt das Spenden-Widget selbst. Beim Humble-Bundle, auf das Jono als Vorbild verweist, gibt man eine Höhe an und verteilt diesen Betrag dann auf die einzelnen Bereiche. Hier ist es anders. Der Nutzer verteilt Geld auf die einzelnen Bereiche und das Widget zeigt dann unten den Gesamtbetrag an, der nicht alleine editierbar ist. Ich vermute, dass so wesentlich schneller höhere Beträge zusammenkommen, weil die Ziele alle unterstützenswert erscheinen und dann die vielen kleinen Einzelspenden sich aufsummieren. Das ist einfach zu testen und genau das wird Canonical gemacht haben. Fies ist, dass der Spendenbetrag unten so aussieht als wäre er in einem editierbaren Textfeld, dabei ist es ein per CSS umgestaltetes <p>-Tag. So wird dem Nutzer Kontrolle vorgegaukelt die er nicht hat.
In die gleiche Kerbe schlägt der Fakt, dass keine Centbeträge bei einzelnen Bereichen möglich sind, wie das beim Humble-Bundle wegen der relativen Verteilung bei editierbaren absoluten Betrag dauernd vorkommt.
-
Dass die Slider bei 2$ beim Laden der Seite stehen, so also 16$ vorgeschlagen werden und trotzdem jeder einzelne Slider fast ganz links ist, dass Maximum liegt bei 125$ pro Slider, ist ein Gestaltungsmittel, um Spendenbeträge möglichst klein erscheinen zu lassen. Und das, obwohl 16$ für ein kostenloses Produkt eine sehr hohe Spende sind.
Es gibt für solche Gestaltungstricks den Begriff "Dark UI Patterns", in diesem Artikel schön erklärt. Diese Spendenseite liegt meiner Meinung nach in einem Graubereich zwischen einer validen Spendenseite mit dem notwendigen Überzeugungsversuch, der scheinbar für die weitere Existenz Canonicals notwendig ist, und einer bewussten Benutzertäuschung. Das könnte man vielen Projekten verzeihen, bei Ubuntu mit dem ganzen Menschlichkeits-Motto finde ich es äußerst unpassend.
Sand
Ein Namenloser, der sich dann Carl nennt, wacht in der Wüste auf und erinnert sich an nichts. Er wird ausgeraubt und verliert die Orientierung, um dann Helen zu treffen und mit ihr die sich auftürmenden Probleme zu versuchen zu lösen. Gleichzeitig gab es einen Mord in einer Hippie-Kommune, jemand stahl Geld, Agenten suchen nach Atombombenteilen und irgendwie geht es um Minen und noch um einiges mehr... es ist ein ziemliches Chaos.
Wolfgang Herrndorf hat in seinem Blog ein Tagebuch über sein Leben mit Hirntumor geschrieben (das ziemlich interessant und traurig ist). Auch die Arbeit zu Sand wird erwähnt, in der Nachbarschaft von Angst, Panik und Psychose. Man merkt dem Roman das schon an. Zum einen an der Orientierungslosigkeit Carls, einzelnen Situationen wie einem Psychiaterverhör und dem generellen Chaos, das Carl in den Weg geworfen wird und mit dem er nicht fertig wird. Klare Verweise auf die Situation Herrndorfs. Zum anderen an der Sprunghaftigkeit des Romans, dass einige Geschichten nur angefangen, aber nie beendet werden, dass der Hauptplot erst nach einem Drittel des Buches halbwegs deutlich wird und auch dann noch chaotisch bleibt. Der Roman ist seltsam.
Sand hat nicht diese Großartigkeit von Tschick, die ich noch beschreiben werde. Trotzdem ist es lesenswert, wenn man die Wirrungen durchbeißt bleibt eine spannende Geschichte und einige tolle Nebenfiguren, wie den Miner mit der Ziege oder die seltsamen Autoren. Und natürlich einige absurde Dialoge und Sätze, die manchmal aber eher den "was ein seltsames Buch"-Effekt verstärken.