diff options
| author | Paul Buetow <paul@buetow.org> | 2025-11-01 16:11:30 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-11-01 16:11:30 +0200 |
| commit | 0d7413660d71a2aa4a6998eaeb1c654b93e1e2c3 (patch) | |
| tree | dc1041387e323b9b78f11031ed4f51574a69886c | |
| parent | fe0a3a0aea02d80786477bb4e7c505966898000d (diff) | |
Update content for html
| -rw-r--r-- | about/resources.html | 200 | ||||
| -rw-r--r-- | about/showcase.html | 6 | ||||
| -rw-r--r-- | gemfeed/2025-11-02-perl-new-features-and-foostats.html (renamed from gemfeed/DRAFT-perl-new-features-and-foostats.html) | 167 | ||||
| -rw-r--r-- | gemfeed/atom.xml | 537 | ||||
| -rw-r--r-- | gemfeed/index.html | 1 | ||||
| -rw-r--r-- | gemfeed/stats.html | 29 | ||||
| -rw-r--r-- | index.html | 5 | ||||
| -rw-r--r-- | uptime-stats.html | 2 |
8 files changed, 709 insertions, 238 deletions
diff --git a/about/resources.html b/about/resources.html index 5d3bb3f8..e4adf255 100644 --- a/about/resources.html +++ b/about/resources.html @@ -50,111 +50,111 @@ <span>In random order:</span><br /> <br /> <ul> -<li>Perl New Features; Joshua McAdams, brian d foy; Perl School</li> -<li>The Docker Book; James Turnbull; Kindle</li> -<li>Data Science at the Command Line; Jeroen Janssens; O'Reilly</li> -<li>Raku Fundamentals; Moritz Lenz; Apress</li> -<li>Funktionale Programmierung; Peter Pepper; Springer</li> +<li>100 Go Mistakes and How to Avoid Them; Teiva Harsanyi; Manning Publications</li> +<li>Effective Java; Joshua Bloch; Addison-Wesley Professional</li> <li>Systems Performance Tuning; Gian-Paolo D. Musumeci and others...; O'Reilly</li> +<li>Funktionale Programmierung; Peter Pepper; Springer</li> +<li>DevOps And Site Reliability Engineering Handbook; Stephen Fleming; Audible</li> +<li>The Docker Book; James Turnbull; Kindle</li> +<li>Systemprogrammierung in Go; Frank MΓΌller; dpunkt</li> +<li>97 things every SRE should know; Emil Stolarsky, Jaime Woo; O'Reilly</li> +<li>The Go Programming Language; Alan A. A. Donovan; Addison-Wesley Professional</li> <li>Effective awk programming; Arnold Robbins; O'Reilly</li> -<li>Object-Oriented Programming with ANSI-C; Axel-Tobias Schreiner</li> -<li>DNS and BIND; Cricket Liu; O'Reilly</li> -<li>Higher Order Perl; Mark Dominus; Morgan Kaufmann</li> -<li>Distributed Systems: Principles and Paradigms; Andrew S. Tanenbaum; Pearson</li> -<li>100 Go Mistakes and How to Avoid Them; Teiva Harsanyi; Manning Publications</li> -<li>The DevOps Handbook; Gene Kim, Jez Humble, Patrick Debois, John Willis; Audible</li> -<li>Polished Ruby Programming; Jeremy Evans; Packt Publishing</li> +<li>Amazon Web Services in Action; Michael Wittig and Andreas Wittig; Manning Publications</li> +<li>The Practise of System and Network Administration; Thomas A. Limoncelli, Christina J. Hogan, Strata R. Chalup; Addison-Wesley Professional Pro Git; Scott Chacon, Ben Straub; Apress</li> +<li>Data Science at the Command Line; Jeroen Janssens; O'Reilly</li> <li>Tmux 2: Productive Mouse-free Development; Brain P. Hogan; The Pragmatic Programmers </li> -<li>Developing Games in Java; David Brackeen and others...; New Riders</li> -<li>Concurrency in Go; Katherine Cox-Buday; O'Reilly</li> -<li>97 things every SRE should know; Emil Stolarsky, Jaime Woo; O'Reilly</li> -<li>Kubernetes Cookbook; Sameer Naik, SΓ©bastien Goasguen, Jonathan Michaux; O'Reilly</li> <li>C++ Programming Language; Bjarne Stroustrup;</li> -<li>Effective Java; Joshua Bloch; Addison-Wesley Professional</li> +<li>Modern Perl; Chromatic ; Onyx Neon Press</li> +<li>Programming Ruby 3.3 (5th Edition); Noel Rappin, with Dave Thomas; The Pragmatic Bookshelf</li> <li>Ultimate Go Notebook; Bill Kennedy</li> -<li>Pro Puppet; James Turnbull, Jeffrey McCune; Apress</li> -<li>Think Raku (aka Think Perl 6); Laurent Rosenfeld, Allen B. Downey; O'Reilly</li> -<li>Chaos Engineering - System Resiliency in Practice; Casey Rosenthal and Nora Jones; eBook</li> +<li>Hands-on Infrastructure Monitoring with Prometheus; Joel Bastos, Pedro Araujo; Packt </li> +<li>Polished Ruby Programming; Jeremy Evans; Packt Publishing</li> +<li>Perl New Features; Joshua McAdams, brian d foy; Perl School</li> <li>Learn You Some Erlang for Great Good; Fred Herbert; No Starch Press</li> -<li>Programming Perl aka "The Camel Book"; Tom Christiansen, brian d foy, Larry Wall & Jon Orwant; O'Reilly</li> -<li>Leanring eBPF; Liz Rice; O'Reilly</li> -<li>Site Reliability Engineering; How Google runs production systems; O'Reilly</li> +<li>The DevOps Handbook; Gene Kim, Jez Humble, Patrick Debois, John Willis; Audible</li> <li>The KCNA (Kubernetes and Cloud Native Associate) Book; Nigel Poulton</li> -<li>DevOps And Site Reliability Engineering Handbook; Stephen Fleming; Audible</li> -<li>21st Century C: C Tips from the New School; Ben Klemens; O'Reilly</li> -<li>Systemprogrammierung in Go; Frank MΓΌller; dpunkt</li> +<li>Distributed Systems: Principles and Paradigms; Andrew S. Tanenbaum; Pearson</li> +<li>Go Brain Teasers - Exercise Your Mind; Miki Tebeka; The Pragmatic Programmers</li> +<li>Concurrency in Go; Katherine Cox-Buday; O'Reilly</li> +<li>Learn You a Haskell for Great Good!; Miran Lipovaca; No Starch Press</li> +<li>Kubernetes Cookbook; Sameer Naik, SΓ©bastien Goasguen, Jonathan Michaux; O'Reilly</li> +<li>DNS and BIND; Cricket Liu; O'Reilly</li> +<li>Programming Perl aka "The Camel Book"; Tom Christiansen, brian d foy, Larry Wall & Jon Orwant; O'Reilly</li> +<li>The Pragmatic Programmer; David Thomas; Addison-Wesley</li> +<li>Think Raku (aka Think Perl 6); Laurent Rosenfeld, Allen B. Downey; O'Reilly</li> +<li>Raku Recipes; J.J. Merelo; Apress</li> <li>Terraform Cookbook; Mikael Krief; Packt Publishing</li> +<li>Developing Games in Java; David Brackeen and others...; New Riders</li> <li>Java ist auch eine Insel; Christian Ullenboom; </li> +<li>Higher Order Perl; Mark Dominus; Morgan Kaufmann</li> +<li>Leanring eBPF; Liz Rice; O'Reilly</li> +<li>Object-Oriented Programming with ANSI-C; Axel-Tobias Schreiner</li> +<li>Chaos Engineering - System Resiliency in Practice; Casey Rosenthal and Nora Jones; eBook</li> +<li>Pro Puppet; James Turnbull, Jeffrey McCune; Apress</li> <li>Clusterbau mit Linux-HA; Michael Schwartzkopff; O'Reilly</li> -<li>Amazon Web Services in Action; Michael Wittig and Andreas Wittig; Manning Publications</li> -<li>The Go Programming Language; Alan A. A. Donovan; Addison-Wesley Professional</li> <li>The Kubernetes Book; Nigel Poulton; Unabridged Audiobook</li> -<li>The Pragmatic Programmer; David Thomas; Addison-Wesley</li> -<li>Programming Ruby 3.3 (5th Edition); Noel Rappin, with Dave Thomas; The Pragmatic Bookshelf</li> -<li>Modern Perl; Chromatic ; Onyx Neon Press</li> -<li>Hands-on Infrastructure Monitoring with Prometheus; Joel Bastos, Pedro Araujo; Packt </li> -<li>Raku Recipes; J.J. Merelo; Apress</li> -<li>Go Brain Teasers - Exercise Your Mind; Miki Tebeka; The Pragmatic Programmers</li> -<li>The Practise of System and Network Administration; Thomas A. Limoncelli, Christina J. Hogan, Strata R. Chalup; Addison-Wesley Professional Pro Git; Scott Chacon, Ben Straub; Apress</li> -<li>Learn You a Haskell for Great Good!; Miran Lipovaca; No Starch Press</li> +<li>Raku Fundamentals; Moritz Lenz; Apress</li> +<li>21st Century C: C Tips from the New School; Ben Klemens; O'Reilly</li> +<li>Site Reliability Engineering; How Google runs production systems; O'Reilly</li> </ul><br /> <h2 style='display: inline' id='technical-references'>Technical references</h2><br /> <br /> <span>I didn't read them from the beginning to the end, but I am using them to look up things. The books are in random order:</span><br /> <br /> <ul> -<li>Algorithms; Robert Sedgewick, Kevin Wayne; Addison Wesley</li> -<li>Implementing Service Level Objectives; Alex Hidalgo; O'Reilly</li> <li>The Linux Programming Interface; Michael Kerrisk; No Starch Press </li> +<li>Implementing Service Level Objectives; Alex Hidalgo; O'Reilly</li> +<li>Relayd and Httpd Mastery; Michael W Lucas</li> +<li>Groovy Kurz & Gut; Joerg Staudemeier; O'Reilly</li> <li>Go: Design Patterns for Real-World Projects; Mat Ryer; Packt</li> <li>BPF Performance Tools - Linux System and Application Observability, Brendan Gregg; Addison Wesley</li> -<li>Groovy Kurz & Gut; Joerg Staudemeier; O'Reilly</li> -<li>Relayd and Httpd Mastery; Michael W Lucas</li> <li>Understanding the Linux Kernel; Daniel P. Bovet, Marco Cesati; O'Reilly</li> +<li>Algorithms; Robert Sedgewick, Kevin Wayne; Addison Wesley</li> </ul><br /> <h2 style='display: inline' id='self-development-and-soft-skills-books'>Self-development and soft-skills books</h2><br /> <br /> <span>In random order:</span><br /> <br /> <ul> -<li>The Courage to Be Disliked; Ichiro Kishimi and Fumitake Koga; Audiobook</li> -<li>Getting Things Done; David Allen</li> <li>The Off Switch; Mark Cropley; Virgin Books (RE-READ 1ST TIME)</li> -<li>Atomic Habits; James Clear; Random House Business</li> -<li>The Bullet Journal Method; Ryder Carroll; Fourth Estate</li> -<li>The Good Enough Job; Simone Stolzoff; Ebury Edge</li> +<li>Ultralearning; Scott Young; Thorsons</li> +<li>Consciousness: A Very Short Introduction; Susan Blackmore; Oxford Uiversity Press</li> +<li>97 Things Every Engineering Manager Should Know; Camille Fournier; Audiobook</li> +<li>Soft Skills; John Sommez; Manning Publications</li> +<li>The Joy of Missing Out; Christina Crook; New Society Publishers</li> +<li>Solve for Happy; Mo Gawdat (RE-READ 1ST TIME)</li> +<li>So Good They Can't Ignore You; Cal Newport; Business Plus</li> +<li>Buddah and Einstein walk into a Bar; Guy Joseph Ale, Claire Bloom; Blackstone Publishing</li> <li>The Software Engineer's Guidebook: Navigating senior, tech lead, and staff engineer positions at tech companies and startups; Gergely Orosz; Audiobook </li> +<li>Meditation for Mortals, Oliver Burkeman, Audiobook</li> +<li>The 7 Habits Of Highly Effective People; Stephen R. Covey; Simon & Schuster UK</li> +<li>Psycho-Cybernetics; Maxwell Maltz; Perigee Books</li> +<li>Eat That Frog; Brian Tracy</li> +<li>The Obstacle Is The Way; Ryan Holiday; Profile Books Ltd</li> +<li>The Daily Stoic; Ryan Holiday, Stephen Hanselman; Profile Books</li> <li>Never Split the Difference; Chris Voss, Tahl Raz; Random House Business</li> +<li>The Bullet Journal Method; Ryder Carroll; Fourth Estate</li> +<li>Atomic Habits; James Clear; Random House Business</li> +<li>The Phoenix Project - A Novel About IT, DevOps, and Helping your Business Win; Gene Kim and Kevin Behr; Trade Select</li> <li>Influence without Authority; A. Cohen, D. Bradford; Wiley</li> -<li>Slow Productivity; Cal Newport; Penguin Random House</li> -<li>Buddah and Einstein walk into a Bar; Guy Joseph Ale, Claire Bloom; Blackstone Publishing</li> +<li>Staff Engineer: Leadership beyond the management track; Will Larson; Audiobook</li> +<li>Digital Minimalism; Cal Newport; Portofolio Penguin</li> <li>Search Inside Yourself - The Unexpected path to Achieving Success, Happiness (and World Peace); Chade-Meng Tan, Daniel Goleman, Jon Kabat-Zinn; HarperOne</li> -<li>Time Management for System Administrators; Thomas A. Limoncelli; O'Reilly</li> <li>The Power of Now; Eckhard Tolle; Yellow Kite</li> +<li>Eat That Frog!; Brian Tracy; Hodder Paperbacks</li> <li>Who Moved My Cheese?; Dr. Spencer Johnson; Vermilion</li> -<li>The Daily Stoic; Ryan Holiday, Stephen Hanselman; Profile Books</li> +<li>The Courage to Be Disliked; Ichiro Kishimi and Fumitake Koga; Audiobook</li> +<li>Stop starting, start finishing; Arne Roock; Lean-Kanban University </li> +<li>Slow Productivity; Cal Newport; Penguin Random House</li> <li>The Complete Software Developer's Career Guide; John Sonmez; Unabridged Audiobook</li> -<li>The 7 Habits Of Highly Effective People; Stephen R. Covey; Simon & Schuster UK</li> +<li>The Good Enough Job; Simone Stolzoff; Ebury Edge</li> +<li>Ultralearning; Anna Laurent; Self-published via Amazon</li> +<li>Getting Things Done; David Allen</li> <li>101 Essays that change the way you think; Brianna Wiest; Audiobook</li> -<li>Ultralearning; Scott Young; Thorsons</li> -<li>Psycho-Cybernetics; Maxwell Maltz; Perigee Books</li> -<li>Digital Minimalism; Cal Newport; Portofolio Penguin</li> -<li>The Joy of Missing Out; Christina Crook; New Society Publishers</li> <li>Deep Work; Cal Newport; Piatkus</li> -<li>So Good They Can't Ignore You; Cal Newport; Business Plus</li> -<li>Stop starting, start finishing; Arne Roock; Lean-Kanban University </li> -<li>Ultralearning; Anna Laurent; Self-published via Amazon</li> +<li>Time Management for System Administrators; Thomas A. Limoncelli; O'Reilly</li> <li>Coders at Work - Reflections on the craft of programming, Peter Seibel and Mitchell Dorian et al., Audiobook</li> -<li>Staff Engineer: Leadership beyond the management track; Will Larson; Audiobook</li> -<li>The Obstacle Is The Way; Ryan Holiday; Profile Books Ltd</li> -<li>Solve for Happy; Mo Gawdat (RE-READ 1ST TIME)</li> -<li>97 Things Every Engineering Manager Should Know; Camille Fournier; Audiobook</li> -<li>Eat That Frog!; Brian Tracy; Hodder Paperbacks</li> -<li>Soft Skills; John Sommez; Manning Publications</li> -<li>Eat That Frog; Brian Tracy</li> -<li>Meditation for Mortals, Oliver Burkeman, Audiobook</li> -<li>The Phoenix Project - A Novel About IT, DevOps, and Helping your Business Win; Gene Kim and Kevin Behr; Trade Select</li> -<li>Consciousness: A Very Short Introduction; Susan Blackmore; Oxford Uiversity Press</li> </ul><br /> <a class='textlink' href='../notes/index.html'>Here are notes of mine for some of the books</a><br /> <br /> @@ -163,31 +163,31 @@ <span>Some of these were in-person with exams; others were online learning lectures only. In random order:</span><br /> <br /> <ul> -<li>Linux Security and Isolation APIs Training; Michael Kerrisk; 3-day on-site training</li> -<li>Cloud Operations on AWS - Learn how to configure, deploy, maintain, and troubleshoot your AWS environments; 3-day online live training with labs; Amazon</li> -<li>Red Hat Certified System Administrator; Course + certification (Although I had the option, I decided not to take the next course as it is more effective to self learn what I need)</li> -<li>MySQL Deep Dive Workshop; 2-day on-site training</li> -<li>The Ultimate Kubernetes Bootcamp; School of Devops; O'Reilly Online</li> -<li>AWS Immersion Day; Amazon; 1-day interactive online training </li> <li>Algorithms Video Lectures; Robert Sedgewick; O'Reilly Online</li> +<li>MySQL Deep Dive Workshop; 2-day on-site training</li> <li>Protocol buffers; O'Reilly Online</li> -<li>Ultimate Go Programming; Bill Kennedy; O'Reilly Online</li> -<li>Scripting Vim; Damian Conway; O'Reilly Online</li> <li>Apache Tomcat Best Practises; 3-day on-site training</li> -<li>Developing IaC with Terraform (with Live Lessons); O'Reilly Online</li> -<li>Structure and Interpretation of Computer Programs; Harold Abelson and more...; </li> +<li>Red Hat Certified System Administrator; Course + certification (Although I had the option, I decided not to take the next course as it is more effective to self learn what I need)</li> +<li>AWS Immersion Day; Amazon; 1-day interactive online training </li> +<li>Cloud Operations on AWS - Learn how to configure, deploy, maintain, and troubleshoot your AWS environments; 3-day online live training with labs; Amazon</li> +<li>The Ultimate Kubernetes Bootcamp; School of Devops; O'Reilly Online</li> +<li>Ultimate Go Programming; Bill Kennedy; O'Reilly Online</li> +<li>F5 Loadbalancers Training; 2-day on-site training; F5, Inc. </li> <li>The Well-Grounded Rubyist Video Edition; David. A. Black; O'Reilly Online</li> +<li>Structure and Interpretation of Computer Programs; Harold Abelson and more...; </li> <li>Functional programming lecture; Remote University of Hagen</li> -<li>F5 Loadbalancers Training; 2-day on-site training; F5, Inc. </li> +<li>Scripting Vim; Damian Conway; O'Reilly Online</li> +<li>Linux Security and Isolation APIs Training; Michael Kerrisk; 3-day on-site training</li> +<li>Developing IaC with Terraform (with Live Lessons); O'Reilly Online</li> </ul><br /> <h2 style='display: inline' id='technical-guides'>Technical guides</h2><br /> <br /> <span>These are not whole books, but guides (smaller or larger) which I found very useful. in random order:</span><br /> <br /> <ul> -<li>Advanced Bash-Scripting Guide </li> <li>How CPUs work at https://cpu.land</li> <li>Raku Guide at https://raku.guide </li> +<li>Advanced Bash-Scripting Guide </li> </ul><br /> <h2 style='display: inline' id='podcasts'>Podcasts</h2><br /> <br /> @@ -196,51 +196,51 @@ <span>In random order:</span><br /> <br /> <ul> -<li>Maintainable</li> -<li>Pratical AI</li> -<li>The ProdCast (Google SRE Podcast)</li> -<li>Wednesday Wisdom</li> <li>Cup o' Go [Golang]</li> -<li>Fork Around And Find Out</li> +<li>The Pragmatic Engineer Podcast</li> +<li>Fallthrough [Golang]</li> +<li>BSD Now [BSD]</li> <li>Deep Questions with Cal Newport</li> +<li>Hidden Brain</li> +<li>The ProdCast (Google SRE Podcast)</li> +<li>Pratical AI</li> +<li>Maintainable</li> +<li>The Changelog Podcast(s)</li> +<li>Fork Around And Find Out</li> <li>Dev Interrupted</li> +<li>Wednesday Wisdom</li> <li>Modern Mentor</li> -<li>BSD Now [BSD]</li> -<li>Fallthrough [Golang]</li> -<li>The Changelog Podcast(s)</li> -<li>The Pragmatic Engineer Podcast</li> <li>Backend Banter</li> -<li>Hidden Brain</li> </ul><br /> <h3 style='display: inline' id='podcasts-i-liked'>Podcasts I liked</h3><br /> <br /> <span>I liked them but am not listening to them anymore. The podcasts have either "finished" (no more episodes) or I stopped listening to them due to time constraints or a shift in my interests.</span><br /> <br /> <ul> +<li>Ship It (predecessor of Fork Around And Find Out)</li> +<li>Go Time (predecessor of fallthrough)</li> <li>CRE: Chaosradio Express [german]</li> -<li>FLOSS weekly</li> <li>Modern Mentor</li> -<li>Go Time (predecessor of fallthrough)</li> +<li>FLOSS weekly</li> <li>Java Pub House</li> -<li>Ship It (predecessor of Fork Around And Find Out)</li> </ul><br /> <h2 style='display: inline' id='newsletters-i-like'>Newsletters I like</h2><br /> <br /> <span>This is a mix of tech and non-tech newsletters I am subscribed to. In random order:</span><br /> <br /> <ul> -<li>VK Newsletter</li> -<li>The Valuable Dev</li> <li>Monospace Mentor</li> -<li>Applied Go Weekly Newsletter</li> -<li>The Pragmatic Engineer</li> -<li>Golang Weekly</li> <li>Ruby Weekly</li> -<li>Register Spill</li> -<li>byteSizeGo</li> <li>Changelog News</li> +<li>The Valuable Dev</li> <li>Andreas Brandhorst Newsletter (Sci-Fi author)</li> +<li>The Pragmatic Engineer</li> +<li>byteSizeGo</li> +<li>Register Spill</li> +<li>Golang Weekly</li> +<li>VK Newsletter</li> <li>The Imperfectionist</li> +<li>Applied Go Weekly Newsletter</li> </ul><br /> <h2 style='display: inline' id='magazines-i-liked'>Magazines I like(d)</h2><br /> <br /> @@ -248,9 +248,9 @@ <br /> <ul> <li>freeX (not published anymore)</li> +<li>LWN (online only)</li> <li>Linux Magazine</li> <li>Linux User</li> -<li>LWN (online only)</li> </ul><br /> <h1 style='display: inline' id='formal-education'>Formal education</h1><br /> <br /> diff --git a/about/showcase.html b/about/showcase.html index af271202..ded87158 100644 --- a/about/showcase.html +++ b/about/showcase.html @@ -107,14 +107,14 @@ <li>π₯ Recent Activity: 24.2 days (avg. age of last 42 commits)</li> <li>βοΈ License: No license found</li> <li>π·οΈ Latest Release: v0.3.0 (2025-10-24)</li> -<li>π€ AI-Assisted: This project was partially created with the help of generative AI</li> +<li>π€ AI-Assisted: This project was vibe-coded.</li> </ul><br /> <br /> <a href='showcase/yoga/image-1.png'><img alt='yoga screenshot' title='yoga screenshot' src='showcase/yoga/image-1.png' /></a><br /> <br /> -<span>Yoga is a terminal-based video browser designed for managing and playing local yoga video collections. It scans a directory (defaulting to <span class='inlinecode'>~/Yoga</span>) for common video formats, probes and caches their durations, and provides a keyboard-driven interface for quickly filtering videos by name, duration range, or tags. Users can sort by name, length, or age, and launch videos directly in VLC with optional crop settingsβall without leaving the terminal. The tool is optimized for quick navigation and playback, making it easy to find and start a specific practice session in seconds.</span><br /> +<span>Yoga is a Terminal User Interface (TUI) application written in Go that helps users browse and play local yoga video collections. It scans a designated directory for video files (MP4, MKV, MOV, AVI, WMV, M4V), extracts and caches duration metadata, and presents them in an interactive table. Users can quickly filter videos by name, duration range, or tags, sort by various criteria (name, length, age), and launch playback in VLC with a single keypress. The tool is particularly useful for managing personal yoga practice libraries where you want to quickly find videos matching specific time constraints or styles without opening a file browser.</span><br /> <br /> -<span>The project is implemented in Go with a TUI interface, organized around a clean <span class='inlinecode'>cmd/yoga</span> entry point that wires together internal packages for filesystem operations (<span class='inlinecode'>internal/fsutil</span>), metadata caching (<span class='inlinecode'>internal/meta</span>), and UI flow (<span class='inlinecode'>internal/app</span>). Video metadata is persisted in <span class='inlinecode'>.video_duration_cache.json</span> files to avoid re-probing on every launch. Development uses Mage for build tasks, enforces β₯85% test coverage, and follows standard Go idioms with <span class='inlinecode'>gofumpt</span> formatting.</span><br /> +<span>The implementation follows clean Go architecture with domain logic organized under <span class='inlinecode'>internal/</span> (including <span class='inlinecode'>app</span> for TUI flow, <span class='inlinecode'>fsutil</span> for filesystem operations, and <span class='inlinecode'>meta</span> for metadata caching). It uses a keyboard-driven interface with vim-like navigation and maintains a <span class='inlinecode'>.video_duration_cache.json</span> file per directory to avoid re-probing video durations on subsequent scans. The project emphasizes maintainability with β₯85% test coverage requirements, table-driven tests, and strict formatting via <span class='inlinecode'>gofumpt</span>, while keeping the entry point minimal in <span class='inlinecode'>cmd/yoga/main.go</span>.</span><br /> <br /> <a class='textlink' href='https://codeberg.org/snonux/yoga'>View on Codeberg</a><br /> <a class='textlink' href='https://github.com/snonux/yoga'>View on GitHub</a><br /> diff --git a/gemfeed/DRAFT-perl-new-features-and-foostats.html b/gemfeed/2025-11-02-perl-new-features-and-foostats.html index cc43cd7d..8e68f616 100644 --- a/gemfeed/DRAFT-perl-new-features-and-foostats.html +++ b/gemfeed/2025-11-02-perl-new-features-and-foostats.html @@ -9,11 +9,16 @@ </head> <body> <p class="header"> -<a href="https://foo.zone">Home</a> | <a href="https://codeberg.org/snonux/foo.zone/src/branch/content-md/gemfeed/DRAFT-perl-new-features-and-foostats.md">Markdown</a> | <a href="gemini://foo.zone/gemfeed/DRAFT-perl-new-features-and-foostats.gmi">Gemini</a> +<a href="https://foo.zone">Home</a> | <a href="https://codeberg.org/snonux/foo.zone/src/branch/content-md/gemfeed/2025-11-02-perl-new-features-and-foostats.md">Markdown</a> | <a href="gemini://foo.zone/gemfeed/2025-11-02-perl-new-features-and-foostats.gmi">Gemini</a> </p> <h1 style='display: inline' id='perl-new-features-and-foostats'>Perl New Features and Foostats</h1><br /> <br /> -<span>Perl just reached rank 10 in the TIOBE index. That headline matches my day-to-day reality because I keep developing the foostats script for simple analytics of my personal websites and Gemini capsules (e.g. <span class='inlinecode'>foo.zone</span>), and almost every Perl release adds new features. The book *Perl New Features* by brian d foy documents the changes well; this post shows how those features look in a real program that runs every morning for my stats generation.</span><br /> +<span class='quote'>Published at 2025-11-01T16:10:35+02:00</span><br /> +<br /> +<span>Perl recently reached rank 10 in the TIOBE index. That headline made me write this blog post as I was developing the Foostats script for simple analytics of my personal websites and Gemini capsules (e.g. <span class='inlinecode'>foo.zone</span>) and there were a couple of new features added to the Perl language over the last releases. The book *Perl New Features* by brian d foy documents the changes well; this post shows how those features look in a real program that runs every morning for my stats generation.</span><br /> +<br /> +<a class='textlink' href='https://developers.slashdot.org/story/25/09/14/0134239/is-perl-the-worlds-10th-most-popular-programming-language'>Perl re-enters the top ten</a><br /> +<a class='textlink' href='https://perlschool.com/books/perl-new-features/'>Perl New Features by Joshua McAdams and brian d foy</a><br /> <br /> <pre> $b="24P7cP3dP31P3bPaP28P24P64P31P2cP24P64P32P2cP24P73P2cP24P67P2cP24P7 @@ -59,7 +64,7 @@ P6 6P 74P3bPaP9P66P6fP72P28P24P6aP3dP30P3bP24P6aP3cP24P6cP3bP24P6aP2bP2bP29P 7bP7dPaP7dP";$b=~s/\s//g;split /P/,$b;foreach(@_){$c.=chr hex};eval $c -The above Perl scripts prints out "Just Another Perl Hacker !" in an +The above Perl script prints out "Just Another Perl Hacker !" in an animation of sorts. </pre> @@ -70,26 +75,24 @@ animation of sorts. <li><a href='#perl-new-features-and-foostats'>Perl New Features and Foostats</a></li> <li>β’ <a href='#motivation'>Motivation</a></li> <li>β’ <a href='#why-i-used-perl'>Why I used Perl</a></li> -<li>β’ <a href='#inside-foostats'>Inside foostats</a></li> +<li>β’ <a href='#inside-foostats'>Inside Foostats</a></li> <li>β’ β’ <a href='#log-pipeline'>Log pipeline</a></li> +<li>β’ β’ <a href='#foooddstxt'><span class='inlinecode'>fooodds.txt</span></a></li> +<li>β’ β’ <a href='#feed-kinds'>Feed kinds</a></li> <li>β’ β’ <a href='#aggregation-and-output'>Aggregation and output</a></li> <li>β’ β’ <a href='#command-line-entry-points'>Command-line entry points</a></li> <li>β’ <a href='#packages-as-real-blocks'>Packages as real blocks</a></li> <li>β’ β’ <a href='#scoped-packages'>Scoped packages</a></li> <li>β’ <a href='#postfix-dereferencing-keeps-data-structures-tidy'>Postfix dereferencing keeps data structures tidy</a></li> <li>β’ β’ <a href='#clear-dereferencing'>Clear dereferencing</a></li> +<li>β’ <a href='#say-is-the-default-voice-now'><span class='inlinecode'>say</span> is the default voice now</a></li> <li>β’ <a href='#lexical-subs-promote-local-reasoning'>Lexical subs promote local reasoning</a></li> -<li>β’ β’ <a href='#helpers-that-stay-local'>Helpers that stay local</a></li> <li>β’ <a href='#reference-aliasing-makes-intent-explicit'>Reference aliasing makes intent explicit</a></li> -<li>β’ β’ <a href='#shared-data-on-purpose'>Shared data on purpose</a></li> <li>β’ <a href='#persistent-state-without-globals'>Persistent state without globals</a></li> <li>β’ β’ <a href='#rate-limiting-state'>Rate limiting state</a></li> <li>β’ β’ <a href='#de-duplicated-logging'>De-duplicated logging</a></li> -<li>β’ <a href='#subroutine-signatures-clarify-every-call-site'>Subroutine signatures clarify every call site</a></li> -<li>β’ β’ <a href='#normal-subroutine-signatures-now'>"normal" subroutine signatures now</a></li> -<li>β’ <a href='#defined-or-assignment-keeps-defaults-obvious'>Defined-or assignment keeps defaults obvious</a></li> -<li>β’ β’ <a href='#defaults-without-boilerplate'>Defaults without boilerplate</a></li> -<li>β’ <a href='#say-is-the-default-voice-now'><span class='inlinecode'>say</span> is the default voice now</a></li> +<li>β’ <a href='#subroutine-signatures'>Subroutine signatures</a></li> +<li>β’ <a href='#defined-or-assignment-for-defaults-without-boilerplate'>Defined-or assignment for defaults without boilerplate</a></li> <li>β’ <a href='#cleanup-with-defer'>Cleanup with <span class='inlinecode'>defer</span></a></li> <li>β’ <a href='#builtins-and-booleans'>Builtins and booleans</a></li> <li>β’ <a href='#conclusion'>Conclusion</a></li> @@ -103,7 +106,7 @@ animation of sorts. <li>Exclude, if possible, any bots and scrapers from the stats</li> <li>Track only anonymized IP addresses, never store raw addresses</li> </ul><br /> -<span>With Foostats I've created a Perl script which does that for my highly opinionated website/blog setup:</span><br /> +<span>With Foostats I've created a Perl script which does that for my highly opinionated website/blog setup, which consists of:</span><br /> <br /> <a class='textlink' href='https://foo.zone/gemfeed/2021-06-05-gemtexter-one-bash-script-to-rule-it-all.html'>Gemtexter, my static site and Gemini capsule generator</a><br /> <a class='textlink' href='https://foo.zone/gemfeed/2024-04-01-KISS-high-availability-with-OpenBSD.html'>How I host this site highly-available using OpenBSD</a><br /> @@ -116,18 +119,21 @@ animation of sorts. <li>I wanted an excuse to explore the newer features of my first programming love.</li> <li>Sometimes, I miss Perl.</li> <li>Perl ships with OpenBSD (the operating system on which my sites run) by default.</li> -<li>It really does live up to its Practical Extraction and Report Language (that's where the name Perl means) for this kind of log grinding I did with foostats.</li> +<li>It really does live up to its Practical Extraction and Report Language (that's what the name Perl means) for this kind of log grinding I did with Foostats.</li> </ul><br /> -<a class='textlink' href='https://developers.slashdot.org/story/25/09/14/0134239/is-perl-the-worlds-10th-most-popular-programming-language'>Perl re-enters the top ten</a><br /> -<a class='textlink' href='https://perlschool.com/books/perl-new-features/'>Perl New Features by Joshua McAdams and brian d foy</a><br /> -<br /> -<h2 style='display: inline' id='inside-foostats'>Inside foostats</h2><br /> +<h2 style='display: inline' id='inside-foostats'>Inside Foostats</h2><br /> <br /> <span>Foostats is simply a log file analyser, which analyses the OpenBSD httpd and relayd logs.</span><br /> <br /> +<a class='textlink' href='https://man.openbsd.org/httpd.8'>https://man.openbsd.org/httpd.8</a><br /> +<a class='textlink' href='https://man.openbsd.org/relayd.8'>https://man.openbsd.org/relayd.8</a><br /> +<br /> <h3 style='display: inline' id='log-pipeline'>Log pipeline</h3><br /> <br /> -<span>A cron job starts Foostats, reads OpenBSD httpd and relayd access logs, and produces the numbers published at <span class='inlinecode'>https://stats.foo.zone</span> and <span class='inlinecode'>gemini://stats.foo.zone</span>. The dashboards are humble because traffic on my sites is still light, yet the trends are interesting for spotting patterns. The script is opinionated (I am repeating myself here, I know), and I will probably be the only one ever using it for my own sites. However, the code demonstrates how Perl's newer features help keep a small script like this exciting and fun!</span><br /> +<span>A CRON job starts Foostats, reads OpenBSD httpd and relayd access logs, and produces the numbers published at <span class='inlinecode'>https://stats.foo.zone</span> and <span class='inlinecode'>gemini://stats.foo.zone</span>. The dashboards are humble because traffic on my sites is still light, yet the trends are interesting for spotting patterns. The script is opinionated (I am repeating myself here, I know), and I will probably be the only one ever using it for my own sites. However, the code demonstrates how Perl's newer features help keep a small script like this exciting and fun!</span><br /> +<br /> +<a class='textlink' href='https://stats.foo.zone'>Foostats (HTTP)</a><br /> +<a class='textlink' href='gemini://stats.foo.zone'>Foostats (Gemini)</a><br /> <br /> <span>On OpenBSD, I've configured the job via the <span class='inlinecode'>daily.local</span> on both of my OpenBSD servers (<span class='inlinecode'>fishfinger.buetow.org</span> and <span class='inlinecode'>blowfish.buetow.org</span> - note one is the master server, the other is the standby server, but the script runs on both and the stats are merged later in the process):</span><br /> <br /> @@ -141,7 +147,13 @@ perl /usr/local/bin/foostats.pl --parse-logs --replicate --report <br /> <span>Internally, <span class='inlinecode'>Foostats::Logreader</span> parses each line of the log files <span class='inlinecode'>/var/log/daemon*</span> and <span class='inlinecode'>/var/www/logs/access_log*</span>, turns timestamps into <span class='inlinecode'>YYYYMMDD/HHMMSS</span> values, hashes IP addresses with SHA3 (for anonymization), and hands a normalized event to <span class='inlinecode'>Foostats::Filter</span>. The filter compares the URI against entries in <span class='inlinecode'>fooodds.txt</span>, tracks how many times an IP address requests within the exact second, and drops anything suspicious (e.g., from web crawlers or malicious attackers). Valid events reach <span class='inlinecode'>Foostats::Aggregator</span>, which counts requests per protocol, records unique visitors for the Gemtext and Atom feeds, and remembers page-level IP sets. <span class='inlinecode'>Foostats::FileOutputter</span> writes the result as gzipped JSON filesβone per day and per protocolβwith IPv4/IPv6 splits, filtered counters, feed readership, and hashes for long URLs.</span><br /> <br /> -<span>Whereas, there are different kinds of feeds:</span><br /> +<h3 style='display: inline' id='foooddstxt'><span class='inlinecode'>fooodds.txt</span></h3><br /> +<br /> +<span><span class='inlinecode'>fooodds.txt</span> is a plain text list of substrings of URLs to be blocked, making it quick to shut down web crawlers. Foostats also detects rapid requests (an indicator of excessive crawling) and blocks the IP. Audit lines are written to <span class='inlinecode'>/var/log/fooodds</span>, which can later be reviewed for false or true positives (I do this around once a month). The <span class='inlinecode'>Justfile</span> even has a <span class='inlinecode'>gather-fooodds</span> target that collects suspicious paths from remote logs so new patterns can be added quickly.</span><br /> +<br /> +<h3 style='display: inline' id='feed-kinds'>Feed kinds</h3><br /> +<br /> +<span>There are different kinds of feeds being tracked by Foostats:</span><br /> <br /> <ul> <li>The Atom web-feed</li> @@ -161,22 +173,20 @@ perl /usr/local/bin/foostats.pl --parse-logs --replicate --report <a class='textlink' href='https://blowfish.buetow.org/foostats/'>https://blowfish.buetow.org/foostats/</a><br /> <a class='textlink' href='https://fishfinger.buetow.org/foostats/'>https://fishfinger.buetow.org/foostats/</a><br /> <br /> -<span>These are the 30-day reports generated:</span><br /> +<span>These are the 30-day reports generated (already linked earlier in this post, but adding here again for clarity):</span><br /> <br /> <a class='textlink' href='gemini://stats.foo.zone'>stats.foo.zone Gemini capsule dashboard</a><br /> <a class='textlink' href='https://stats.foo.zone'>stats.foo.zone HTTP dashboard</a><br /> <br /> <h3 style='display: inline' id='command-line-entry-points'>Command-line entry points</h3><br /> <br /> -<span><span class='inlinecode'>foostats_main</span> is the command entry point. <span class='inlinecode'>--parse-logs</span> refreshes the gzipped files, <span class='inlinecode'>--replicate</span> runs the cross-host sync, and <span class='inlinecode'>--report</span> rebuilds the HTML and Gemini report pages. <span class='inlinecode'>--all</span> performs everything in one go. Defaults point to <span class='inlinecode'>/var/www/htdocs/buetow.org/self/foostats</span> for data, <span class='inlinecode'>/var/gemini/stats.foo.zone</span> for Gemtext output, and <span class='inlinecode'>/var/www/htdocs/gemtexter/stats.foo.zone</span> for HTML output. Replication always forces the three most recent days worth of the data across HTTPS and leaves older files untouched to save bandwidth.</span><br /> -<br /> -<span><span class='inlinecode'>fooodds.txt</span> is a plain text list of substrings of URLs to be blocked, making it quick to shut down web crawlers. Foostats also detects rapid requests (an indicator of excessive crawling) and blocks the IP. Audit lines are written to <span class='inlinecode'>/var/log/fooodds</span>, which can later be reviewed for false or true positives (I do this around once a month). The <span class='inlinecode'>Justfile</span> even has a <span class='inlinecode'>gather-fooodds</span> target that collects suspicious paths from remote logs so new patterns can be added quickly.</span><br /> +<span><span class='inlinecode'>foostats_main</span> is the command entry point. <span class='inlinecode'>--parse-logs</span> refreshes the gzipped files, <span class='inlinecode'>--replicate</span> runs the cross-host sync, and <span class='inlinecode'>--report</span> rebuilds the HTML and Gemini report pages. <span class='inlinecode'>--all</span> performs everything in one go. Defaults point to <span class='inlinecode'>/var/www/htdocs/buetow.org/self/foostats</span> for data, <span class='inlinecode'>/var/gemini/stats.foo.zone</span> for Gemtext output, and <span class='inlinecode'>/var/www/htdocs/gemtexter/stats.foo.zone</span> for HTML output. Replication always forces the three most recent days' worth of data across HTTPS and leaves older files untouched to save bandwidth.</span><br /> <br /> <span>The complete source lives on Codeberg here:</span><br /> <br /> -<a class='textlink' href='https://codeberg.org/snonux/foostats'>foostats on Codeberg</a><br /> +<a class='textlink' href='https://codeberg.org/snonux/foostats'>Foostats on Codeberg</a><br /> <br /> -<span>Now let's go to some new Perl features: </span><br /> +<span>Now let's go to some new Perl features:</span><br /> <br /> <h2 style='display: inline' id='packages-as-real-blocks'>Packages as real blocks</h2><br /> <br /> @@ -252,7 +262,7 @@ http://www.gnu.org/software/src-highlite --> } </pre> <br /> -<span>You see that this feature becomes increasingly useful the with nested data structures, e.g. to print all keys of the nested hash:</span><br /> +<span>You see that this feature becomes increasingly useful with nested data structures, e.g. to print all keys of the nested hash:</span><br /> <br /> <!-- Generator: GNU source-highlight 3.1.9 by Lorenzo Bettini @@ -263,17 +273,51 @@ http://www.gnu.org/software/src-highlite --> <br /> <span>Loops over like <span class='inlinecode'>$stats->{page_ips}->{urls}->%*</span> or <span class='inlinecode'>$merge{$key}->{$_}->%*</span> show which level of the structure is in play. The merger in Foostats updates host and URL statistics without building temporary arrays, and the reporter code mirrors the layout of the final tables. Before postfix dereferencing, the same code relied on braces within braces and was harder to read.</span><br /> <br /> -<h2 style='display: inline' id='lexical-subs-promote-local-reasoning'>Lexical subs promote local reasoning</h2><br /> +<h2 style='display: inline' id='say-is-the-default-voice-now'><span class='inlinecode'>say</span> is the default voice now</h2><br /> +<br /> +<span><span class='inlinecode'>say</span> became the default once the script switched to <span class='inlinecode'>use v5.38;</span>. It adds a newline to every message printed, comparable to Ruby's <span class='inlinecode'>puts</span>, making log messages like "Processing $path" or "Writing report to $report_path" cleaner:</span><br /> +<br /> +<!-- Generator: GNU source-highlight 3.1.9 +by Lorenzo Bettini +http://www.lorenzobettini.it +http://www.gnu.org/software/src-highlite --> +<pre><b><u><font color="#000000">use</font></u></b> v5.<font color="#000000">38</font>; + +<b><u><font color="#000000">print</font></u></b> <font color="#808080">"Hello, world!\n"</font>; <i><font color="silver"># old way</font></i> + +say <font color="#808080">"Hello, world!"</font>; <i><font color="silver"># new way</font></i> +</pre> <br /> -<h3 style='display: inline' id='helpers-that-stay-local'>Helpers that stay local</h3><br /> +<h2 style='display: inline' id='lexical-subs-promote-local-reasoning'>Lexical subs promote local reasoning</h2><br /> <br /> <span>Lexical subroutines keep helpers close to the code that needs them. In <span class='inlinecode'>Foostats::Logreader::parse_web_logs</span>, functions such as <span class='inlinecode'>my sub parse_date</span> and <span class='inlinecode'>my sub open_file</span> live only inside that scope.</span><br /> <br /> -<h2 style='display: inline' id='reference-aliasing-makes-intent-explicit'>Reference aliasing makes intent explicit</h2><br /> +<span>This is an example of a lexical sub named <span class='inlinecode'>trim</span>, which is only visible within the outer sub named <span class='inlinecode'>process_lines</span>:</span><br /> <br /> -<h3 style='display: inline' id='shared-data-on-purpose'>Shared data on purpose</h3><br /> +<!-- Generator: GNU source-highlight 3.1.9 +by Lorenzo Bettini +http://www.lorenzobettini.it +http://www.gnu.org/software/src-highlite --> +<pre><b><u><font color="#000000">use</font></u></b> v5.<font color="#000000">38</font>; + +<b><u><font color="#000000">sub</font></u></b> process_lines { + <b><u><font color="#000000">my</font></u></b> @lines = @_; + + <b><u><font color="#000000">my</font></u></b> <b><u><font color="#000000">sub</font></u></b> trim ($str) { + $str =~ <b><u><font color="#000000">s</font></u></b><font color="#808080">/^\s+|\s+$//</font>gr; + } + + <b><u><font color="#000000">return</font></u></b> [ <b><u><font color="#000000">map</font></u></b> { trim($_) } @lines ]; +} + +<b><u><font color="#000000">my</font></u></b> @raw = (<font color="#808080">" foo "</font>, <font color="#808080">" bar"</font>, <font color="#808080">"baz "</font>); +<b><u><font color="#000000">my</font></u></b> $cleaned = process_lines(@raw); +say <b><u><font color="#000000">for</font></u></b> @$cleaned; <i><font color="silver"># prints "foo", "bar", "baz"</font></i> +</pre> <br /> -<span>Ref aliasing is enabled with <span class='inlinecode'>use feature qw(refaliasing)</span> and helps communicate intent more clearly (if you remember the Perl syntax, of course. Otherwise, it's like chinese). The filter starts with <span class='inlinecode'>\my $uri_path = \$event->{uri_path}</span> so any later modification touches the original event.</span><br /> +<h2 style='display: inline' id='reference-aliasing-makes-intent-explicit'>Reference aliasing makes intent explicit</h2><br /> +<br /> +<span>Reference aliasing can be enabled with <span class='inlinecode'>use feature qw(refaliasing)</span> and helps communicate intent more clearly (if you remember the Perl syntax, of courseβotherwise, it can look rather cryptic). The filter starts with <span class='inlinecode'>\my $uri_path = \$event->{uri_path}</span> so any later modification touches the original event. This is an example with ref aliasing in action:</span><br /> <br /> <!-- Generator: GNU source-highlight 3.1.9 by Lorenzo Bettini @@ -288,7 +332,7 @@ $foo = <font color="#000000">99</font>; <b><u><font color="#000000">print</font></u></b> $hash->{foo}; <i><font color="silver"># prints 99</font></i> </pre> <br /> -<span>The aggregator in Foostats aliases <span class='inlinecode'>$self->{stats}{$date_key}</span> before updating counters, so the structure remains intact. Combined with subroutine signatures, this makes it obvious when a piece of data is shared instead of copied, preventing silent bugs.</span><br /> +<span>The aggregator in Foostats aliases <span class='inlinecode'>$self->{stats}{$date_key}</span> before updating counters, so the structure remains intact. Combined with subroutine signatures, this makes it obvious when a piece of data is shared instead of copied, preventing silent bugs. This enables having shorter names for long nested data structures.</span><br /> <br /> <h2 style='display: inline' id='persistent-state-without-globals'>Persistent state without globals</h2><br /> <br /> @@ -311,17 +355,19 @@ say counter(); <i><font color="silver"># 2</font></i> say counter(); <i><font color="silver"># 3</font></i> </pre> <br /> +<span>Hash and array state variables have been supported since <span class='inlinecode'>state</span> arrived in Perl 5.10. Scalar state variables were already supported previously.</span><br /> +<br /> <h3 style='display: inline' id='rate-limiting-state'>Rate limiting state</h3><br /> <br /> -<span>In Foostats, <span class='inlinecode'>state</span> variables store run-specific state without using package globals. <span class='inlinecode'>state %blocked</span> remembers IP hashes that already triggered the odd-request filter, and <span class='inlinecode'>state $last_time</span> and <span class='inlinecode'>state %count</span> track how many requests an IP makes in the exact second. Hash and array state variables have been supported since <span class='inlinecode'>state</span> arrived in Perl 5.10, so this code takes advantage of that long-standing capability. However, what's new is that hashes can now also be state variables.</span><br /> +<span>In Foostats, <span class='inlinecode'>state</span> variables store run-specific state without using package globals. <span class='inlinecode'>state %blocked</span> remembers IP hashes that already triggered the odd-request filter, and <span class='inlinecode'>state $last_time</span> and <span class='inlinecode'>state %count</span> track how many requests an IP makes in the exact second.</span><br /> <br /> <h3 style='display: inline' id='de-duplicated-logging'>De-duplicated logging</h3><br /> <br /> -<span><span class='inlinecode'>state %dedup</span> keeps the log output to one warning per URI. Early versions utilized global hashes for the same tasks, producing inconsistent results during tests. Switching to <span class='inlinecode'>state</span> removed those edge cases.</span><br /> +<span><span class='inlinecode'>state %dedup</span> keeps the log output of the suspicious calls to one warning per URI. Early versions utilized global hashes for the same tasks, producing inconsistent results during tests. Switching to <span class='inlinecode'>state</span> removed those edge cases.</span><br /> <br /> -<h2 style='display: inline' id='subroutine-signatures-clarify-every-call-site'>Subroutine signatures clarify every call site</h2><br /> +<h2 style='display: inline' id='subroutine-signatures'>Subroutine signatures</h2><br /> <br /> -<span>Perl now supports subroutine signatures like other modern languages do. Foostats uses them everywhere.</span><br /> +<span>Perl now supports subroutine signatures like other modern languages do. Foostats uses them everywhere. Examples:</span><br /> <br /> <!-- Generator: GNU source-highlight 3.1.9 by Lorenzo Bettini @@ -331,39 +377,48 @@ http://www.gnu.org/software/src-highlite --> <b><u><font color="#000000">sub</font></u></b> greet_old { <b><u><font color="#000000">my</font></u></b> $name = <b><u><font color="#000000">shift</font></u></b>; <b><u><font color="#000000">print</font></u></b> <font color="#808080">"Hello, $name!\n"</font> } <i><font color="silver"># Another old way</font></i> -<b><u><font color="#000000">sub</font></u></b> greet_old ($) { $name = <b><u><font color="#000000">shift</font></u></b>; <b><u><font color="#000000">print</font></u></b> <font color="#808080">"Hello, $name!\n"</font> } +<b><u><font color="#000000">sub</font></u></b> greet_old2 ($) { <b><u><font color="#000000">my</font></u></b> $name = <b><u><font color="#000000">shift</font></u></b>; <b><u><font color="#000000">print</font></u></b> <font color="#808080">"Hello, $name!\n"</font> } <i><font color="silver"># New way</font></i> <b><u><font color="#000000">sub</font></u></b> greet ($name) { say <font color="#808080">"Hello, $name!"</font>; } greet(<font color="#808080">"Alice"</font>); <i><font color="silver"># prints "Hello, Alice!"</font></i> - -<b><u><font color="#000000">sub</font></u></b> greet ($name) { - say <font color="#808080">"Hello, $name!"</font>; -} - -greet(<font color="#808080">"Alice"</font>); <i><font color="silver"># prints "Hello, Alice!"</font></i> </pre> <br /> -<h3 style='display: inline' id='normal-subroutine-signatures-now'>"normal" subroutine signatures now</h3><br /> +<span>In Foostats, constructors declare <span class='inlinecode'>sub new ($class, $odds_file, $log_path)</span>, anonymous callbacks expose <span class='inlinecode'>sub ($event)</span>, and helper subs list the values they expect, e.g.:</span><br /> <br /> -<span>Subroutine signatures are active throughout foostats. Constructors declare <span class='inlinecode'>sub new ($class, $odds_file, $log_path)</span>, anonymous callbacks expose <span class='inlinecode'>sub ($event)</span>, and helper subs list the values they expect.</span><br /> -<br /> -<h2 style='display: inline' id='defined-or-assignment-keeps-defaults-obvious'>Defined-or assignment keeps defaults obvious</h2><br /> -<br /> -<h3 style='display: inline' id='defaults-without-boilerplate'>Defaults without boilerplate</h3><br /> +<!-- Generator: GNU source-highlight 3.1.9 +by Lorenzo Bettini +http://www.lorenzobettini.it +http://www.gnu.org/software/src-highlite --> +<pre><b><u><font color="#000000">my</font></u></b> $anon = <b><u><font color="#000000">sub</font></u></b> ($name) { + say <font color="#808080">"Hello, $name!"</font>; +}; + +$anon->(<font color="#808080">"World"</font>); <i><font color="silver"># prints "Hello, World!"</font></i> +</pre> <br /> -<span>The operator <span class='inlinecode'>//=</span> keeps configuration and counters simple. Environment variables may be missing when cron runs the script, so <span class='inlinecode'>//=</span>, combined with signatures, sets defaults without warnings. </span><br /> +<h2 style='display: inline' id='defined-or-assignment-for-defaults-without-boilerplate'>Defined-or assignment for defaults without boilerplate</h2><br /> <br /> -<h2 style='display: inline' id='say-is-the-default-voice-now'><span class='inlinecode'>say</span> is the default voice now</h2><br /> +<span>The operator <span class='inlinecode'>//=</span> keeps configuration and counters simple. Environment variables may be missing when CRON runs the script, so <span class='inlinecode'>//=</span>, combined with signatures, sets defaults without warnings. Example use of that operator:</span><br /> <br /> -<span><span class='inlinecode'>say</span> became the default once the script switched to <span class='inlinecode'>use v5.38;</span>. Log messages such as "Processing $path" or "Writing report to $report_path". It adds a newline to every message printed, comparable to Ruby's <span class='inlinecode'>put</span>.</span><br /> +<!-- Generator: GNU source-highlight 3.1.9 +by Lorenzo Bettini +http://www.lorenzobettini.it +http://www.gnu.org/software/src-highlite --> +<pre><b><u><font color="#000000">my</font></u></b> $foo; +$foo <font color="#808080">//</font>= <font color="#000000">42</font>; +say $foo; <i><font color="silver"># prints 42</font></i> + +$foo <font color="#808080">//</font>= <font color="#000000">99</font>; +say $foo; <i><font color="silver"># still prints 42, because $foo was already defined</font></i> +</pre> <br /> <h2 style='display: inline' id='cleanup-with-defer'>Cleanup with <span class='inlinecode'>defer</span></h2><br /> <br /> -<span>Even though not used in Foostats, this (borrowed from Go?) feature is neat to have in Perl now.</span><br /> +<span>Even though not used in Foostats, this feature (similar to Go's defer) is neat to have in Perl now.</span><br /> <br /> -<span>The <span class='inlinecode'>defer</span> block (<span class='inlinecode'>use feature 'defer"</span>) schedules a piece of code to run when the current scope exits, regardless of how it exits (e.g. normal return, exception). This is perfect for ensuring resources, such as file handles, are closed. <span class='inlinecode'>Foostats::Logreader</span> uses it to make sure log files are always closed, even if parsing fails mid-way.</span><br /> +<span>The <span class='inlinecode'>defer</span> block (<span class='inlinecode'>use feature 'defer"</span>) schedules a piece of code to run when the current scope exits, regardless of how it exits (e.g. normal return, exception). This is perfect for ensuring resources, such as file handles, are closed.</span><br /> <br /> <!-- Generator: GNU source-highlight 3.1.9 by Lorenzo Bettini @@ -387,11 +442,11 @@ http://www.gnu.org/software/src-highlite --> <br /> <h2 style='display: inline' id='builtins-and-booleans'>Builtins and booleans</h2><br /> <br /> -<span>The script also utilises other modern additions that often go unnoticed. <span class='inlinecode'>use builtin qw(true false);</span> combined with <span class='inlinecode'>experimental::builtin</span> provides more real boolean values.</span><br /> +<span>The script also utilizes other modern additions that often go unnoticed. <span class='inlinecode'>use builtin qw(true false);</span> combined with <span class='inlinecode'>experimental::builtin</span> provides more real boolean values.</span><br /> <br /> <h2 style='display: inline' id='conclusion'>Conclusion</h2><br /> <br /> -<span>I want to code more in Perl again. The newer features make it a joy to write small scripts like Foostats. If you haven't looked at Perl in a while, give it another try! The main thing which holds me back from writing more Perl is the lack of good tooling. For example, there is no proper LSP and tree sitter support available, which would work as well as for Go and Ruby.</span><br /> +<span>I want to code more in Perl again. The newer features make it a joy to write small scripts like Foostats. If you haven't looked at Perl in a while, give it another try! The main thing which holds me back from writing more Perl is the lack of good tooling. For example, there is no proper LSP and tree sitter support available, which would work as good as the ones available for Go and Ruby.</span><br /> <br /> <span>E-Mail your comments to <span class='inlinecode'>paul@nospam.buetow.org</span> :-)</span><br /> <br /> diff --git a/gemfeed/atom.xml b/gemfeed/atom.xml index d7a99259..559b2f00 100644 --- a/gemfeed/atom.xml +++ b/gemfeed/atom.xml @@ -1,12 +1,472 @@ <?xml version="1.0" encoding="utf-8"?> <feed xmlns="http://www.w3.org/2005/Atom"> - <updated>2025-10-28T20:14:24+02:00</updated> + <updated>2025-11-01T16:10:35+02:00</updated> <title>foo.zone feed</title> <subtitle>To be in the .zone!</subtitle> <link href="https://foo.zone/gemfeed/atom.xml" rel="self" /> <link href="https://foo.zone/" /> <id>https://foo.zone/</id> <entry> + <title>Perl New Features and Foostats</title> + <link href="https://foo.zone/gemfeed/2025-11-02-perl-new-features-and-foostats.html" /> + <id>https://foo.zone/gemfeed/2025-11-02-perl-new-features-and-foostats.html</id> + <updated>2025-11-01T16:10:35+02:00</updated> + <author> + <name>Paul Buetow aka snonux</name> + <email>paul@dev.buetow.org</email> + </author> + <summary>Perl recently reached rank 10 in the TIOBE index. That headline made me write this blog post as I was developing the Foostats script for simple analytics of my personal websites and Gemini capsules (e.g. `foo.zone`) and there were a couple of new features added to the Perl language over the last releases. The book *Perl New Features* by brian d foy documents the changes well; this post shows how those features look in a real program that runs every morning for my stats generation.</summary> + <content type="xhtml"> + <div xmlns="http://www.w3.org/1999/xhtml"> + <h1 style='display: inline' id='perl-new-features-and-foostats'>Perl New Features and Foostats</h1><br /> +<br /> +<span>Perl recently reached rank 10 in the TIOBE index. That headline made me write this blog post as I was developing the Foostats script for simple analytics of my personal websites and Gemini capsules (e.g. <span class='inlinecode'>foo.zone</span>) and there were a couple of new features added to the Perl language over the last releases. The book *Perl New Features* by brian d foy documents the changes well; this post shows how those features look in a real program that runs every morning for my stats generation.</span><br /> +<br /> +<a class='textlink' href='https://developers.slashdot.org/story/25/09/14/0134239/is-perl-the-worlds-10th-most-popular-programming-language'>Perl re-enters the top ten</a><br /> +<a class='textlink' href='https://perlschool.com/books/perl-new-features/'>Perl New Features by Joshua McAdams and brian d foy</a><br /> +<br /> +<pre> +$b="24P7cP3dP31P3bPaP28P24P64P31P2cP24P64P32P2cP24P73P2cP24P67P2cP24P7 +2P29P3dP28P22P31P30P30P30P30P22P2cP22P31P30P30P30P30P30P22P2cP22P4aP75 +P7 3P +74 P2 +0P 41P6eP6fP74P 68P65P72P20P50 P65P72P6cP2 0P48P 61 +P6 3P6bP65P72P22P 29P3bPaP40P6dP 3dP73P70P6cP6 9P74P 20 +P2 fP2fP 2cP22P 2cP2eP3aP21P2 bP2aP 30P4f P40P2 2P +3b PaP24 P6eP3 dP6c P65P6 eP67 P74P6 8P +20 P24P7 3P3bP aP24 P75P3 dP22 P20P2 2P +78 P24P6 eP3bP aPaP 70P72 P69P 6eP74 P2 +0P 22P5c P6eP20 P20P 24P75 P5cP7 2P22P 3b +Pa PaP66P6fP72P2 8P24P7aP20P 3dP20P31P3bP 20P24 P7 +aP 3cP3dP24P6 eP3bP20P24 P7aP2bP2bP 29P20 P7 +bP aPaP9 P77P28P24P6 4P31P29P 3bPaP 9P +24 P72P3 dP69 P6eP74P28 P72P6 1P +6e P64P2 8P24 P6eP2 9P29P 3bPaP 9P +24 P67P3 dP73 P75P6 2P73P 74P72 P2 +0P 24P73 P2cP24P72P2cP 31P3b PaP9P 24P67P20P3fP20 P6 +4P 6fP20 P9P7bP20PaP9P9 P9P9P 9P66P 6fP72P20P28P24 P6 +bP 3dP30 P3bP24P6bP3cP3 9P3bP 24P6bP 2bP2bP29P20P7b Pa +P9 P9 +P9 P9 +P9 P9P73P75P6 2P73 P74P 72P2 8P24P75P2c P24P72 P2 +cP 31P29P3dP24P 6dP5 bP24 P6bP 5dP3bP20Pa P9P9 P9P9 P9 +P9 P70P 72P69 P6eP 74P2 0P22 P20P20P24P 75P 5cP 72 +P2 2P3b PaP9 P9P9 P9P9 P9P7 7P28 P24 P6 4P +32 P29P 3bPa P9P9 P9P9 P9P7 dPaP 9P9 P9 +P9 P9P7 3P75 P62P 73P7 4P72 P28P 24P7 5P +2c P24P 72P2c P31P 29P3 dP24 P67P3bP20P aP9P9 P9 +P9 P7dP20PaP9P 9P3a P20P 72P6 5P64P6fP3b PaP9 P7 +3P 75P62P73P 74P7 2P28 P24P 73P2cP24P7 2P2c P3 +1P 29P3dP2 2P30 P22P 3bPa P9P7 0P7 2P +69 P6eP74P2 0P22 P20P 20P2 4P75 P5c P7 +2P 22P3 bPaPa P7dP aPaP 77P2 0P28 P24 P6 +4P 32P2 9P3bP aP70 P72P 69P6 eP74 P2 0P2 2P +20 P20P 24P75 P20P21P5cP7 2P22P3bPaP 73P6cP65P6 5P7 0P20 P3 +2P 3bPa P70P7 2P69P6eP74P 20P22P20P2 0P24P75P20 P21P 5cP6 eP +22 P3bP aPaP7 3P75P62P2 0P77P20P7b PaP9P24P6c P3dP73 P6 +8P 69 +P6 6P +74P3bPaP9P66P6fP72P28P24P6aP3dP30P3bP24P6aP3cP24P6cP3bP24P6aP2bP2bP29P +7bP7dPaP7dP";$b=~s/\s//g;split /P/,$b;foreach(@_){$c.=chr hex};eval $c + +The above Perl script prints out "Just Another Perl Hacker !" in an +animation of sorts. + +</pre> +<br /> +<h2 style='display: inline' id='table-of-contents'>Table of Contents</h2><br /> +<br /> +<ul> +<li><a href='#perl-new-features-and-foostats'>Perl New Features and Foostats</a></li> +<li>β’ <a href='#motivation'>Motivation</a></li> +<li>β’ <a href='#why-i-used-perl'>Why I used Perl</a></li> +<li>β’ <a href='#inside-foostats'>Inside Foostats</a></li> +<li>β’ β’ <a href='#log-pipeline'>Log pipeline</a></li> +<li>β’ β’ <a href='#foooddstxt'><span class='inlinecode'>fooodds.txt</span></a></li> +<li>β’ β’ <a href='#feed-kinds'>Feed kinds</a></li> +<li>β’ β’ <a href='#aggregation-and-output'>Aggregation and output</a></li> +<li>β’ β’ <a href='#command-line-entry-points'>Command-line entry points</a></li> +<li>β’ <a href='#packages-as-real-blocks'>Packages as real blocks</a></li> +<li>β’ β’ <a href='#scoped-packages'>Scoped packages</a></li> +<li>β’ <a href='#postfix-dereferencing-keeps-data-structures-tidy'>Postfix dereferencing keeps data structures tidy</a></li> +<li>β’ β’ <a href='#clear-dereferencing'>Clear dereferencing</a></li> +<li>β’ <a href='#say-is-the-default-voice-now'><span class='inlinecode'>say</span> is the default voice now</a></li> +<li>β’ <a href='#lexical-subs-promote-local-reasoning'>Lexical subs promote local reasoning</a></li> +<li>β’ <a href='#reference-aliasing-makes-intent-explicit'>Reference aliasing makes intent explicit</a></li> +<li>β’ <a href='#persistent-state-without-globals'>Persistent state without globals</a></li> +<li>β’ β’ <a href='#rate-limiting-state'>Rate limiting state</a></li> +<li>β’ β’ <a href='#de-duplicated-logging'>De-duplicated logging</a></li> +<li>β’ <a href='#subroutine-signatures'>Subroutine signatures</a></li> +<li>β’ <a href='#defined-or-assignment-for-defaults-without-boilerplate'>Defined-or assignment for defaults without boilerplate</a></li> +<li>β’ <a href='#cleanup-with-defer'>Cleanup with <span class='inlinecode'>defer</span></a></li> +<li>β’ <a href='#builtins-and-booleans'>Builtins and booleans</a></li> +<li>β’ <a href='#conclusion'>Conclusion</a></li> +</ul><br /> +<h2 style='display: inline' id='motivation'>Motivation</h2><br /> +<br /> +<span>I've been running <span class='inlinecode'>foo.zone</span> for a while now, but I've never looked into visitor statistics or analytics. I value privacyβnot just my own, but also the privacy of others (the visitors of this site) β so I hesitated to use any off-the-shelf analytics plugins. All I wanted to collect were:</span><br /> +<br /> +<ul> +<li>Which blog posts had the most (unique) visitors</li> +<li>Exclude, if possible, any bots and scrapers from the stats</li> +<li>Track only anonymized IP addresses, never store raw addresses</li> +</ul><br /> +<span>With Foostats I've created a Perl script which does that for my highly opinionated website/blog setup, which consists of:</span><br /> +<br /> +<a class='textlink' href='https://foo.zone/gemfeed/2021-06-05-gemtexter-one-bash-script-to-rule-it-all.html'>Gemtexter, my static site and Gemini capsule generator</a><br /> +<a class='textlink' href='https://foo.zone/gemfeed/2024-04-01-KISS-high-availability-with-OpenBSD.html'>How I host this site highly-available using OpenBSD</a><br /> +<br /> +<h2 style='display: inline' id='why-i-used-perl'>Why I used Perl</h2><br /> +<br /> +<span>Even though nowadays I code more in Go and Ruby, I stuck with Perl for Foostats for three simple reasons:</span><br /> +<br /> +<ul> +<li>I wanted an excuse to explore the newer features of my first programming love.</li> +<li>Sometimes, I miss Perl.</li> +<li>Perl ships with OpenBSD (the operating system on which my sites run) by default.</li> +<li>It really does live up to its Practical Extraction and Report Language (that's what the name Perl means) for this kind of log grinding I did with Foostats.</li> +</ul><br /> +<h2 style='display: inline' id='inside-foostats'>Inside Foostats</h2><br /> +<br /> +<span>Foostats is simply a log file analyser, which analyses the OpenBSD httpd and relayd logs.</span><br /> +<br /> +<a class='textlink' href='https://man.openbsd.org/httpd.8'>https://man.openbsd.org/httpd.8</a><br /> +<a class='textlink' href='https://man.openbsd.org/relayd.8'>https://man.openbsd.org/relayd.8</a><br /> +<br /> +<h3 style='display: inline' id='log-pipeline'>Log pipeline</h3><br /> +<br /> +<span>A CRON job starts Foostats, reads OpenBSD httpd and relayd access logs, and produces the numbers published at <span class='inlinecode'>https://stats.foo.zone</span> and <span class='inlinecode'>https://stats.foo.zone</span>. The dashboards are humble because traffic on my sites is still light, yet the trends are interesting for spotting patterns. The script is opinionated (I am repeating myself here, I know), and I will probably be the only one ever using it for my own sites. However, the code demonstrates how Perl's newer features help keep a small script like this exciting and fun!</span><br /> +<br /> +<a class='textlink' href='https://stats.foo.zone'>Foostats (HTTP)</a><br /> +<a class='textlink' href='https://stats.foo.zone'>Foostats (Gemini)</a><br /> +<br /> +<span>On OpenBSD, I've configured the job via the <span class='inlinecode'>daily.local</span> on both of my OpenBSD servers (<span class='inlinecode'>fishfinger.buetow.org</span> and <span class='inlinecode'>blowfish.buetow.org</span> - note one is the master server, the other is the standby server, but the script runs on both and the stats are merged later in the process):</span><br /> +<br /> +<!-- Generator: GNU source-highlight 3.1.9 +by Lorenzo Bettini +http://www.lorenzobettini.it +http://www.gnu.org/software/src-highlite --> +<pre>fishfinger$ grep foostats /etc/daily.<b><u><font color="#000000">local</font></u></b> +perl /usr/local/bin/foostats.pl --parse-logs --replicate --report +</pre> +<br /> +<span>Internally, <span class='inlinecode'>Foostats::Logreader</span> parses each line of the log files <span class='inlinecode'>/var/log/daemon*</span> and <span class='inlinecode'>/var/www/logs/access_log*</span>, turns timestamps into <span class='inlinecode'>YYYYMMDD/HHMMSS</span> values, hashes IP addresses with SHA3 (for anonymization), and hands a normalized event to <span class='inlinecode'>Foostats::Filter</span>. The filter compares the URI against entries in <span class='inlinecode'>fooodds.txt</span>, tracks how many times an IP address requests within the exact second, and drops anything suspicious (e.g., from web crawlers or malicious attackers). Valid events reach <span class='inlinecode'>Foostats::Aggregator</span>, which counts requests per protocol, records unique visitors for the Gemtext and Atom feeds, and remembers page-level IP sets. <span class='inlinecode'>Foostats::FileOutputter</span> writes the result as gzipped JSON filesβone per day and per protocolβwith IPv4/IPv6 splits, filtered counters, feed readership, and hashes for long URLs.</span><br /> +<br /> +<h3 style='display: inline' id='foooddstxt'><span class='inlinecode'>fooodds.txt</span></h3><br /> +<br /> +<span><span class='inlinecode'>fooodds.txt</span> is a plain text list of substrings of URLs to be blocked, making it quick to shut down web crawlers. Foostats also detects rapid requests (an indicator of excessive crawling) and blocks the IP. Audit lines are written to <span class='inlinecode'>/var/log/fooodds</span>, which can later be reviewed for false or true positives (I do this around once a month). The <span class='inlinecode'>Justfile</span> even has a <span class='inlinecode'>gather-fooodds</span> target that collects suspicious paths from remote logs so new patterns can be added quickly.</span><br /> +<br /> +<h3 style='display: inline' id='feed-kinds'>Feed kinds</h3><br /> +<br /> +<span>There are different kinds of feeds being tracked by Foostats:</span><br /> +<br /> +<ul> +<li>The Atom web-feed</li> +<li>The same feed via Gemini</li> +<li>The Gemfeed (a special format popular in the Geminispace)</li> +</ul><br /> +<h3 style='display: inline' id='aggregation-and-output'>Aggregation and output</h3><br /> +<br /> +<span>As mentioned, Foostats merges the stats from both hosts, master and standby. For the master-standby setup description, read:</span><br /> +<br /> +<a class='textlink' href='./2024-04-01-KISS-high-availability-with-OpenBSD.html'>KISS high-availability with OpenBSD</a><br /> +<br /> +<span>Those gzipped files land in <span class='inlinecode'>stats/</span>. From there, <span class='inlinecode'>Foostats::Replicator</span> can pull matching files from the partner host (<span class='inlinecode'>fishfinger</span> or <span class='inlinecode'>blowfish</span>) so the view covers both servers, <span class='inlinecode'>Foostats::Merger</span> combines them into daily summaries, and <span class='inlinecode'>Foostats::Reporter</span> rebuilds Gemtext and HTML reports.</span><br /> +<br /> +<span>Those are the raw stats files:</span><br /> +<br /> +<a class='textlink' href='https://blowfish.buetow.org/foostats/'>https://blowfish.buetow.org/foostats/</a><br /> +<a class='textlink' href='https://fishfinger.buetow.org/foostats/'>https://fishfinger.buetow.org/foostats/</a><br /> +<br /> +<span>These are the 30-day reports generated (already linked earlier in this post, but adding here again for clarity):</span><br /> +<br /> +<a class='textlink' href='https://stats.foo.zone'>stats.foo.zone Gemini capsule dashboard</a><br /> +<a class='textlink' href='https://stats.foo.zone'>stats.foo.zone HTTP dashboard</a><br /> +<br /> +<h3 style='display: inline' id='command-line-entry-points'>Command-line entry points</h3><br /> +<br /> +<span><span class='inlinecode'>foostats_main</span> is the command entry point. <span class='inlinecode'>--parse-logs</span> refreshes the gzipped files, <span class='inlinecode'>--replicate</span> runs the cross-host sync, and <span class='inlinecode'>--report</span> rebuilds the HTML and Gemini report pages. <span class='inlinecode'>--all</span> performs everything in one go. Defaults point to <span class='inlinecode'>/var/www/htdocs/buetow.org/self/foostats</span> for data, <span class='inlinecode'>/var/gemini/stats.foo.zone</span> for Gemtext output, and <span class='inlinecode'>/var/www/htdocs/gemtexter/stats.foo.zone</span> for HTML output. Replication always forces the three most recent days' worth of data across HTTPS and leaves older files untouched to save bandwidth.</span><br /> +<br /> +<span>The complete source lives on Codeberg here:</span><br /> +<br /> +<a class='textlink' href='https://codeberg.org/snonux/foostats'>Foostats on Codeberg</a><br /> +<br /> +<span>Now let's go to some new Perl features:</span><br /> +<br /> +<h2 style='display: inline' id='packages-as-real-blocks'>Packages as real blocks</h2><br /> +<br /> +<h3 style='display: inline' id='scoped-packages'>Scoped packages</h3><br /> +<br /> +<span>Recent Perl versions allow the block form <span class='inlinecode'>package Foo { ... }</span>. Foostats uses it for every package. Imports stay local to the block, helper subs do not leak into the global symbol table, and configuration happens where the code needs it.</span><br /> +<br /> +<span>The old way:</span><br /> +<br /> +<!-- Generator: GNU source-highlight 3.1.9 +by Lorenzo Bettini +http://www.lorenzobettini.it +http://www.gnu.org/software/src-highlite --> +<pre><b><u><font color="#000000">package</font></u></b> foo; + +<b><u><font color="#000000">sub</font></u></b> hello { + <b><u><font color="#000000">print</font></u></b> <font color="#808080">"Hello from package foo\n"</font>; +} + +<b><u><font color="#000000">package</font></u></b> bar; + +<b><u><font color="#000000">sub</font></u></b> hello { + <b><u><font color="#000000">print</font></u></b> <font color="#808080">"Hello from package bar\n"</font>; +} + +<font color="#000000">1</font> +</pre> +<br /> +<span>But now it is also possible to do this:</span><br /> +<br /> +<!-- Generator: GNU source-highlight 3.1.9 +by Lorenzo Bettini +http://www.lorenzobettini.it +http://www.gnu.org/software/src-highlite --> +<pre><b><u><font color="#000000">package</font></u></b> foo { + <b><u><font color="#000000">sub</font></u></b> hello { + <b><u><font color="#000000">print</font></u></b> <font color="#808080">"Hello from package foo\n"</font>; + } +} + +<b><u><font color="#000000">package</font></u></b> bar { + <b><u><font color="#000000">sub</font></u></b> hello { + <b><u><font color="#000000">print</font></u></b> <font color="#808080">"Hello from package bar\n"</font>; + } +} +</pre> +<br /> +<h2 style='display: inline' id='postfix-dereferencing-keeps-data-structures-tidy'>Postfix dereferencing keeps data structures tidy</h2><br /> +<br /> +<h3 style='display: inline' id='clear-dereferencing'>Clear dereferencing</h3><br /> +<br /> +<span>The script handles nested hashes and arrays. Postfix dereferencing (<span class='inlinecode'>$hash->%*</span>, <span class='inlinecode'>$array->@*</span>) keeps that readable.</span><br /> +<br /> +<span>E.g. instead of having to write:</span><br /> +<br /> +<!-- Generator: GNU source-highlight 3.1.9 +by Lorenzo Bettini +http://www.lorenzobettini.it +http://www.gnu.org/software/src-highlite --> +<pre><b><u><font color="#000000">for</font></u></b> <b><u><font color="#000000">my</font></u></b> $elem (@{$array_ref}) { + <b><u><font color="#000000">print</font></u></b> <font color="#808080">"$elem\n"</font>; +} +</pre> +<br /> +<span>one can now do:</span><br /> +<br /> +<!-- Generator: GNU source-highlight 3.1.9 +by Lorenzo Bettini +http://www.lorenzobettini.it +http://www.gnu.org/software/src-highlite --> +<pre><b><u><font color="#000000">for</font></u></b> <b><u><font color="#000000">my</font></u></b> $elem ($array_ref->@*) { + <b><u><font color="#000000">print</font></u></b> <font color="#808080">"$elem\n"</font>; +} +</pre> +<br /> +<span>You see that this feature becomes increasingly useful with nested data structures, e.g. to print all keys of the nested hash:</span><br /> +<br /> +<!-- Generator: GNU source-highlight 3.1.9 +by Lorenzo Bettini +http://www.lorenzobettini.it +http://www.gnu.org/software/src-highlite --> +<pre><b><u><font color="#000000">print</font></u></b> <b><u><font color="#000000">for</font></u></b> <b><u><font color="#000000">keys</font></u></b> $hash->{stats}->%*; +</pre> +<br /> +<span>Loops over like <span class='inlinecode'>$stats->{page_ips}->{urls}->%*</span> or <span class='inlinecode'>$merge{$key}->{$_}->%*</span> show which level of the structure is in play. The merger in Foostats updates host and URL statistics without building temporary arrays, and the reporter code mirrors the layout of the final tables. Before postfix dereferencing, the same code relied on braces within braces and was harder to read.</span><br /> +<br /> +<h2 style='display: inline' id='say-is-the-default-voice-now'><span class='inlinecode'>say</span> is the default voice now</h2><br /> +<br /> +<span><span class='inlinecode'>say</span> became the default once the script switched to <span class='inlinecode'>use v5.38;</span>. It adds a newline to every message printed, comparable to Ruby's <span class='inlinecode'>puts</span>, making log messages like "Processing $path" or "Writing report to $report_path" cleaner:</span><br /> +<br /> +<!-- Generator: GNU source-highlight 3.1.9 +by Lorenzo Bettini +http://www.lorenzobettini.it +http://www.gnu.org/software/src-highlite --> +<pre><b><u><font color="#000000">use</font></u></b> v5.<font color="#000000">38</font>; + +<b><u><font color="#000000">print</font></u></b> <font color="#808080">"Hello, world!\n"</font>; <i><font color="silver"># old way</font></i> + +say <font color="#808080">"Hello, world!"</font>; <i><font color="silver"># new way</font></i> +</pre> +<br /> +<h2 style='display: inline' id='lexical-subs-promote-local-reasoning'>Lexical subs promote local reasoning</h2><br /> +<br /> +<span>Lexical subroutines keep helpers close to the code that needs them. In <span class='inlinecode'>Foostats::Logreader::parse_web_logs</span>, functions such as <span class='inlinecode'>my sub parse_date</span> and <span class='inlinecode'>my sub open_file</span> live only inside that scope.</span><br /> +<br /> +<span>This is an example of a lexical sub named <span class='inlinecode'>trim</span>, which is only visible within the outer sub named <span class='inlinecode'>process_lines</span>:</span><br /> +<br /> +<!-- Generator: GNU source-highlight 3.1.9 +by Lorenzo Bettini +http://www.lorenzobettini.it +http://www.gnu.org/software/src-highlite --> +<pre><b><u><font color="#000000">use</font></u></b> v5.<font color="#000000">38</font>; + +<b><u><font color="#000000">sub</font></u></b> process_lines { + <b><u><font color="#000000">my</font></u></b> @lines = @_; + + <b><u><font color="#000000">my</font></u></b> <b><u><font color="#000000">sub</font></u></b> trim ($str) { + $str =~ <b><u><font color="#000000">s</font></u></b><font color="#808080">/^\s+|\s+$//</font>gr; + } + + <b><u><font color="#000000">return</font></u></b> [ <b><u><font color="#000000">map</font></u></b> { trim($_) } @lines ]; +} + +<b><u><font color="#000000">my</font></u></b> @raw = (<font color="#808080">" foo "</font>, <font color="#808080">" bar"</font>, <font color="#808080">"baz "</font>); +<b><u><font color="#000000">my</font></u></b> $cleaned = process_lines(@raw); +say <b><u><font color="#000000">for</font></u></b> @$cleaned; <i><font color="silver"># prints "foo", "bar", "baz"</font></i> +</pre> +<br /> +<h2 style='display: inline' id='reference-aliasing-makes-intent-explicit'>Reference aliasing makes intent explicit</h2><br /> +<br /> +<span>Reference aliasing can be enabled with <span class='inlinecode'>use feature qw(refaliasing)</span> and helps communicate intent more clearly (if you remember the Perl syntax, of courseβotherwise, it can look rather cryptic). The filter starts with <span class='inlinecode'>\my $uri_path = \$event->{uri_path}</span> so any later modification touches the original event. This is an example with ref aliasing in action:</span><br /> +<br /> +<!-- Generator: GNU source-highlight 3.1.9 +by Lorenzo Bettini +http://www.lorenzobettini.it +http://www.gnu.org/software/src-highlite --> +<pre><b><u><font color="#000000">use</font></u></b> feature <b><u><font color="#000000">qw</font></u></b>(refaliasing); + +<b><u><font color="#000000">my</font></u></b> $hash = { foo => <font color="#000000">42</font> }; +\<b><u><font color="#000000">my</font></u></b> $foo = \$hash->{foo}; + +$foo = <font color="#000000">99</font>; +<b><u><font color="#000000">print</font></u></b> $hash->{foo}; <i><font color="silver"># prints 99</font></i> +</pre> +<br /> +<span>The aggregator in Foostats aliases <span class='inlinecode'>$self->{stats}{$date_key}</span> before updating counters, so the structure remains intact. Combined with subroutine signatures, this makes it obvious when a piece of data is shared instead of copied, preventing silent bugs. This enables having shorter names for long nested data structures.</span><br /> +<br /> +<h2 style='display: inline' id='persistent-state-without-globals'>Persistent state without globals</h2><br /> +<br /> +<span>A Perl state variable is declared with <span class='inlinecode'>state $var</span> and retains its value between calls to the enclosing subroutine. Foostats uses that for rate limiting and de-duplicated logging.</span><br /> +<br /> +<span>This is a small example demonstrating the use of a state variable in Perl:</span><br /> +<br /> +<!-- Generator: GNU source-highlight 3.1.9 +by Lorenzo Bettini +http://www.lorenzobettini.it +http://www.gnu.org/software/src-highlite --> +<pre><b><u><font color="#000000">sub</font></u></b> counter { + state $count = <font color="#000000">0</font>; + $count++; + <b><u><font color="#000000">return</font></u></b> $count; +} + +say counter(); <i><font color="silver"># 1</font></i> +say counter(); <i><font color="silver"># 2</font></i> +say counter(); <i><font color="silver"># 3</font></i> +</pre> +<br /> +<span>Hash and array state variables have been supported since <span class='inlinecode'>state</span> arrived in Perl 5.10. Scalar state variables were already supported previously.</span><br /> +<br /> +<h3 style='display: inline' id='rate-limiting-state'>Rate limiting state</h3><br /> +<br /> +<span>In Foostats, <span class='inlinecode'>state</span> variables store run-specific state without using package globals. <span class='inlinecode'>state %blocked</span> remembers IP hashes that already triggered the odd-request filter, and <span class='inlinecode'>state $last_time</span> and <span class='inlinecode'>state %count</span> track how many requests an IP makes in the exact second.</span><br /> +<br /> +<h3 style='display: inline' id='de-duplicated-logging'>De-duplicated logging</h3><br /> +<br /> +<span><span class='inlinecode'>state %dedup</span> keeps the log output of the suspicious calls to one warning per URI. Early versions utilized global hashes for the same tasks, producing inconsistent results during tests. Switching to <span class='inlinecode'>state</span> removed those edge cases.</span><br /> +<br /> +<h2 style='display: inline' id='subroutine-signatures'>Subroutine signatures</h2><br /> +<br /> +<span>Perl now supports subroutine signatures like other modern languages do. Foostats uses them everywhere. Examples:</span><br /> +<br /> +<!-- Generator: GNU source-highlight 3.1.9 +by Lorenzo Bettini +http://www.lorenzobettini.it +http://www.gnu.org/software/src-highlite --> +<pre><i><font color="silver"># Old way</font></i> +<b><u><font color="#000000">sub</font></u></b> greet_old { <b><u><font color="#000000">my</font></u></b> $name = <b><u><font color="#000000">shift</font></u></b>; <b><u><font color="#000000">print</font></u></b> <font color="#808080">"Hello, $name!\n"</font> } + +<i><font color="silver"># Another old way</font></i> +<b><u><font color="#000000">sub</font></u></b> greet_old2 ($) { <b><u><font color="#000000">my</font></u></b> $name = <b><u><font color="#000000">shift</font></u></b>; <b><u><font color="#000000">print</font></u></b> <font color="#808080">"Hello, $name!\n"</font> } + +<i><font color="silver"># New way</font></i> +<b><u><font color="#000000">sub</font></u></b> greet ($name) { say <font color="#808080">"Hello, $name!"</font>; } + +greet(<font color="#808080">"Alice"</font>); <i><font color="silver"># prints "Hello, Alice!"</font></i> +</pre> +<br /> +<span>In Foostats, constructors declare <span class='inlinecode'>sub new ($class, $odds_file, $log_path)</span>, anonymous callbacks expose <span class='inlinecode'>sub ($event)</span>, and helper subs list the values they expect, e.g.:</span><br /> +<br /> +<!-- Generator: GNU source-highlight 3.1.9 +by Lorenzo Bettini +http://www.lorenzobettini.it +http://www.gnu.org/software/src-highlite --> +<pre><b><u><font color="#000000">my</font></u></b> $anon = <b><u><font color="#000000">sub</font></u></b> ($name) { + say <font color="#808080">"Hello, $name!"</font>; +}; + +$anon->(<font color="#808080">"World"</font>); <i><font color="silver"># prints "Hello, World!"</font></i> +</pre> +<br /> +<h2 style='display: inline' id='defined-or-assignment-for-defaults-without-boilerplate'>Defined-or assignment for defaults without boilerplate</h2><br /> +<br /> +<span>The operator <span class='inlinecode'>//=</span> keeps configuration and counters simple. Environment variables may be missing when CRON runs the script, so <span class='inlinecode'>//=</span>, combined with signatures, sets defaults without warnings. Example use of that operator:</span><br /> +<br /> +<!-- Generator: GNU source-highlight 3.1.9 +by Lorenzo Bettini +http://www.lorenzobettini.it +http://www.gnu.org/software/src-highlite --> +<pre><b><u><font color="#000000">my</font></u></b> $foo; +$foo <font color="#808080">//</font>= <font color="#000000">42</font>; +say $foo; <i><font color="silver"># prints 42</font></i> + +$foo <font color="#808080">//</font>= <font color="#000000">99</font>; +say $foo; <i><font color="silver"># still prints 42, because $foo was already defined</font></i> +</pre> +<br /> +<h2 style='display: inline' id='cleanup-with-defer'>Cleanup with <span class='inlinecode'>defer</span></h2><br /> +<br /> +<span>Even though not used in Foostats, this feature (similar to Go's defer) is neat to have in Perl now.</span><br /> +<br /> +<span>The <span class='inlinecode'>defer</span> block (<span class='inlinecode'>use feature 'defer"</span>) schedules a piece of code to run when the current scope exits, regardless of how it exits (e.g. normal return, exception). This is perfect for ensuring resources, such as file handles, are closed.</span><br /> +<br /> +<!-- Generator: GNU source-highlight 3.1.9 +by Lorenzo Bettini +http://www.lorenzobettini.it +http://www.gnu.org/software/src-highlite --> +<pre><b><u><font color="#000000">use</font></u></b> feature <b><u><font color="#000000">qw</font></u></b>(defer); + +<b><u><font color="#000000">sub</font></u></b> parse_log_file { + <b><u><font color="#000000">my</font></u></b> ($path) = @_; + <b><u><font color="#000000">open</font></u></b> <b><u><font color="#000000">my</font></u></b> $fh, <font color="#808080">'<'</font>, $path or <b><u><font color="#000000">die</font></u></b> <font color="#808080">"Cannot open $path: $!"</font>; + defer { <b><u><font color="#000000">close</font></u></b> $fh }; + + <b><u><font color="#000000">while</font></u></b> (<b><u><font color="#000000">my</font></u></b> $line = <font color="#808080"><$fh></font>) { + <i><font color="silver"># ... parsing logic that might throw an exception ...</font></i> + } + <i><font color="silver"># $fh is automatically closed here</font></i> +} +</pre> +<br /> +<span>This pattern replaces manual <span class='inlinecode'>close</span> calls in every exit path of the subroutine and is more robust than relying solely on object destructors.</span><br /> +<br /> +<h2 style='display: inline' id='builtins-and-booleans'>Builtins and booleans</h2><br /> +<br /> +<span>The script also utilizes other modern additions that often go unnoticed. <span class='inlinecode'>use builtin qw(true false);</span> combined with <span class='inlinecode'>experimental::builtin</span> provides more real boolean values.</span><br /> +<br /> +<h2 style='display: inline' id='conclusion'>Conclusion</h2><br /> +<br /> +<span>I want to code more in Perl again. The newer features make it a joy to write small scripts like Foostats. If you haven't looked at Perl in a while, give it another try! The main thing which holds me back from writing more Perl is the lack of good tooling. For example, there is no proper LSP and tree sitter support available, which would work as good as the ones available for Go and Ruby.</span><br /> +<br /> +<span>E-Mail your comments to <span class='inlinecode'>paul@nospam.buetow.org</span> :-)</span><br /> +<br /> +<span>Other related posts are:</span><br /> +<br /> +<a class='textlink' href='./2023-05-01-unveiling-guprecords:-uptime-records-with-raku.html'>2023-05-01 Unveiling <span class='inlinecode'>guprecords.raku</span>: Global Uptime Records with Raku</a><br /> +<a class='textlink' href='./2022-05-27-perl-is-still-a-great-choice.html'>2022-05-27 Perl is still a great choice</a><br /> +<a class='textlink' href='./2011-05-07-perl-daemon-service-framework.html'>2011-05-07 Perl Daemon (Service Framework)</a><br /> +<a class='textlink' href='./2008-06-26-perl-poetry.html'>2008-06-26 Perl Poetry</a><br /> +<br /> +<a class='textlink' href='../'>Back to the main site</a><br /> + </div> + </content> + </entry> + <entry> <title>Key Takeaways from The Well-Grounded Rubyist</title> <link href="https://foo.zone/gemfeed/2025-10-11-key-takeaways-from-the-well-grounded-rubyist.html" /> <id>https://foo.zone/gemfeed/2025-10-11-key-takeaways-from-the-well-grounded-rubyist.html</id> @@ -14404,79 +14864,4 @@ http://www.gnu.org/software/src-highlite --> </div> </content> </entry> - <entry> - <title>Site Reliability Engineering - Part 1: SRE and Organizational Culture</title> - <link href="https://foo.zone/gemfeed/2023-08-18-site-reliability-engineering-part-1.html" /> - <id>https://foo.zone/gemfeed/2023-08-18-site-reliability-engineering-part-1.html</id> - <updated>2023-08-18T22:43:47+03:00</updated> - <author> - <name>Paul Buetow aka snonux</name> - <email>paul@dev.buetow.org</email> - </author> - <summary>Being a Site Reliability Engineer (SRE) is like stepping into a lively, ever-evolving universe. The world of SRE mixes together different tech, a unique culture, and a whole lot of determination. Itβs one of the toughest but most exciting jobs out there. There's zero chance of getting bored because there's always a fresh challenge to tackle and new technology to play around with. It's not just about the tech side of things either; it's heavily rooted in communication, collaboration, and teamwork. As someone currently working as an SRE, Iβm here to break it all down for you in this blog series. Let's dive into what SRE is really all about!</summary> - <content type="xhtml"> - <div xmlns="http://www.w3.org/1999/xhtml"> - <h1 style='display: inline' id='site-reliability-engineering---part-1-sre-and-organizational-culture'>Site Reliability Engineering - Part 1: SRE and Organizational Culture</h1><br /> -<br /> -<span class='quote'>Published at 2023-08-18T22:43:47+03:00</span><br /> -<br /> -<span>Being a Site Reliability Engineer (SRE) is like stepping into a lively, ever-evolving universe. The world of SRE mixes together different tech, a unique culture, and a whole lot of determination. Itβs one of the toughest but most exciting jobs out there. There's zero chance of getting bored because there's always a fresh challenge to tackle and new technology to play around with. It's not just about the tech side of things either; it's heavily rooted in communication, collaboration, and teamwork. As someone currently working as an SRE, Iβm here to break it all down for you in this blog series. Let's dive into what SRE is really all about!</span><br /> -<br /> -<a class='textlink' href='./2023-08-18-site-reliability-engineering-part-1.html'>2023-08-18 Site Reliability Engineering - Part 1: SRE and Organizational Culture (You are currently reading this)</a><br /> -<a class='textlink' href='./2023-11-19-site-reliability-engineering-part-2.html'>2023-11-19 Site Reliability Engineering - Part 2: Operational Balance</a><br /> -<a class='textlink' href='./2024-01-09-site-reliability-engineering-part-3.html'>2024-01-09 Site Reliability Engineering - Part 3: On-Call Culture</a><br /> -<a class='textlink' href='./2024-09-07-site-reliability-engineering-part-4.html'>2024-09-07 Site Reliability Engineering - Part 4: Onboarding for On-Call Engineers</a><br /> -<br /> -<pre> -ββββββ - -DC on fire: - - ββ ββ ββ - ββ ββ ββββ ββ ββ ββββ ββ - ββββββ ββ ββββ ββββ ββββ - ββββ ββββββ ββ ββ ββ ββββββ ββ - ββββ ββββββββββ ββ ββββ ββββββ ββββββββββ ββββ - ββββ ββββββββββ ββββ ββββββ ββββββ ββββββββββ ββββββ - ββββββ ββββββββββββ ββββ ββββββββ ββββββββββββββ ββββββββββββ ββββββββ - ββββββββββββββββββββββββββββ ββββββββββ ββββββββββββββββ ββββββββββββββββββββββ - ββββββββββββββββββββββββββββ ββββββββββ ββββββββββββββββββββββββββββββββββββββββββββ - ββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββββββββββββββ - ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ - ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ - ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ - ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ - ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ -</pre> -<br /> -<h2 style='display: inline' id='sre-and-organizational-culture-navigating-the-nexus'>SRE and Organizational Culture: Navigating the Nexus</h2><br /> -<br /> -<span>At the core of SRE is the principle of "prevention over cure." Unlike traditional IT setups that mostly react to problems, SRE focuses on spotting issues before they happen. This proactive approach involves using Service Level Indicators (SLIs) and Service Level Objectives (SLOs). These tools give teams specific metrics and targets to aim for, helping them keep systems reliable and users happy. It's all about creating a culture that prioritizes user experience and makes sure everything runs smoothly to meet their needs.</span><br /> -<br /> -<span>Another key concept in SRE is the "error budget." Itβs a clever approach that recognizes no system is perfect and that failures will happen. Instead of punishing mistakes, SRE culture embraces them as chances to learn and improve. The idea is to give teams a "budget" for errors, creating a space where innovation can thrive and failures are simply seen as lessons learned.</span><br /> -<br /> -<span>SRE isn't just about tech and metrics; it's also about people. It tackles the "hero culture" that often ends up burning out IT teams. Sure, having a hero swoop in to save the day can be great, but relying on that all the time just isnβt sustainable. Instead, SRE focuses on collective expertise and teamwork. It recognizes that heroes are at their best within a solid team, making the need for constant heroics unnecessary. This way of thinking promotes a balanced on-call experience and highlights trust, ownership, good communication, and collaboration as key to success. I've been there myself, falling into the hero trap, and I know firsthand that it's just not feasible to be the go-to person for every problem that comes up.</span><br /> -<br /> -<span>Also, the SRE model puts a big emphasis on good documentation. It's not enough to just have docs; they need to be top-notch and go through the same quality checks as code. This really helps with onboarding new team members, training, and keeping everyone on the same page.</span><br /> -<br /> -<span>Adopting SRE can be a big challenge for some organizations. They might think the SRE approach goes against their goals, like preferring to roll out new features quickly rather than focusing on reliability, or seeing SRE practices as too much hassle. Building an SRE culture often means taking the time to explain things patiently and showing the benefits, like faster release cycles and a better user experience.</span><br /> -<br /> -<span>Monitoring and observability are also big parts of SRE, highlighting the need for top-notch tools to query and analyze data. This aligns with the SRE focus on continuous learning and being adaptable. SREs naturally need to be curious, ready to dive into any strange issues, and always open to picking up new tools and practices.</span><br /> -<br /> -<span>For SRE to really work in any organization, everyone needs to buy into its principles. It's about moving away from working in isolated silos and relying on SRE to just patch things up. Instead, itβs about making reliability a shared responsibility across the whole team.</span><br /> -<br /> -<span>In short, bringing SRE principles into the mix goes beyond just the technical stuff. It helps shift the whole organizational culture to value things like preventing issues before they happen, always learning, working together, and being open with communication. When SRE and corporate culture blend well, you end up with not just reliable systems but also a strong, resilient, and forward-thinking workplace.</span><br /> -<br /> -<span>Organizations that have SLIs, SLOs, and error budgets in place are already pretty far along in their SRE journey. Getting there takes a lot of communication, convincing people, and patience.</span><br /> -<br /> -<span>Continue with the second part of this series:</span><br /> -<br /> -<a class='textlink' href='./2023-11-19-site-reliability-engineering-part-2.html'>2023-11-19 Site Reliability Engineering - Part 2: Operational Balance</a><br /> -<br /> -<span>E-Mail your comments to <span class='inlinecode'>paul@nospam.buetow.org</span> :-)</span><br /> -<br /> -<a class='textlink' href='../'>Back to the main site</a><br /> - </div> - </content> - </entry> </feed> diff --git a/gemfeed/index.html b/gemfeed/index.html index 71bbc19c..37f89e8f 100644 --- a/gemfeed/index.html +++ b/gemfeed/index.html @@ -15,6 +15,7 @@ <br /> <h2 style='display: inline' id='to-be-in-the-zone'>To be in the .zone!</h2><br /> <br /> +<a class='textlink' href='./2025-11-02-perl-new-features-and-foostats.html'>2025-11-02 - Perl New Features and Foostats</a><br /> <a class='textlink' href='./2025-10-11-key-takeaways-from-the-well-grounded-rubyist.html'>2025-10-11 - Key Takeaways from The Well-Grounded Rubyist</a><br /> <a class='textlink' href='./2025-10-02-f3s-kubernetes-with-freebsd-part-7.html'>2025-10-02 - f3s: Kubernetes with FreeBSD - Part 7: k3s and first pod deployments</a><br /> <a class='textlink' href='./2025-09-14-bash-golf-part-4.html'>2025-09-14 - Bash Golf Part 4</a><br /> diff --git a/gemfeed/stats.html b/gemfeed/stats.html new file mode 100644 index 00000000..36f1bfc5 --- /dev/null +++ b/gemfeed/stats.html @@ -0,0 +1,29 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> +<title>Stats</title> +<link rel="shortcut icon" type="image/gif" href="/favicon.ico" /> +<link rel="stylesheet" href="../style.css" /> +<link rel="stylesheet" href="style-override.css" /> +</head> +<body> +<p class="header"> +<a href="https://foo.zone">Home</a> | <a href="https://codeberg.org/snonux/foo.zone/src/branch/content-md/gemfeed/stats.md">Markdown</a> | <a href="gemini://foo.zone/gemfeed/stats.gmi">Gemini</a> +</p> +<h1 style='display: inline' id='stats'>Stats</h1><br /> +<br /> +<span>Here, you can find some statistics!</span><br /> +<br /> +<a class='textlink' href='./uptime-stats.html'>My machine uptime statistics</a><br /> +<a class='textlink' href='https://stats.foo.zone'>Site statistics (HTTP)</a><br /> +<a class='textlink' href='gemini://stats.foo.zone'>Site statistics (Gemini)</a><br /> +<p class="footer"> + Generated with <a href="https://codeberg.org/snonux/gemtexter">Gemtexter 3.0.1-develop</a> | + served by <a href="https://www.OpenBSD.org">OpenBSD</a>/<a href="https://man.openbsd.org/relayd.8">relayd(8)</a>+<a href="https://man.openbsd.org/httpd.8">httpd(8)</a> | + <a href="https://foo.zone/site-mirrors.html">Site Mirrors</a> + <br /> + Webring: <a href="https://shring.sh/foo.zone/previous">previous</a> | <a href="https://shring.sh">shring</a> | <a href="https://shring.sh/foo.zone/next">next</a> +</p> +</body> +</html> @@ -13,7 +13,7 @@ </p> <h1 style='display: inline' id='hello'>Hello!</h1><br /> <br /> -<span class='quote'>This site was generated at 2025-10-31T20:26:55+02:00 by <span class='inlinecode'>Gemtexter</span></span><br /> +<span class='quote'>This site was generated at 2025-11-01T16:10:35+02:00 by <span class='inlinecode'>Gemtexter</span></span><br /> <br /> <span>Welcome to the foo.zone!</span><br /> <br /> @@ -22,8 +22,8 @@ <h2 style='display: inline' id='some-links'>Some links</h2><br /> <br /> <a class='textlink' href='./about/index.html'>About me</a><br /> -<a class='textlink' href='./uptime-stats.html'>My machine uptime statistics</a><br /> <a class='textlink' href='./gemfeed/2021-04-24-welcome-to-the-geminispace.html'>Welcome to the Geminispace</a><br /> +<a class='textlink' href='./stats.html'>Some stats</a><br /> <br /> <h3 style='display: inline' id='webring'>Webring</h3><br /> <br /> @@ -51,6 +51,7 @@ <br /> <h3 style='display: inline' id='posts'>Posts</h3><br /> <br /> +<a class='textlink' href='./gemfeed/2025-11-02-perl-new-features-and-foostats.html'>2025-11-02 - Perl New Features and Foostats</a><br /> <a class='textlink' href='./gemfeed/2025-10-11-key-takeaways-from-the-well-grounded-rubyist.html'>2025-10-11 - Key Takeaways from The Well-Grounded Rubyist</a><br /> <a class='textlink' href='./gemfeed/2025-10-02-f3s-kubernetes-with-freebsd-part-7.html'>2025-10-02 - f3s: Kubernetes with FreeBSD - Part 7: k3s and first pod deployments</a><br /> <a class='textlink' href='./gemfeed/2025-09-14-bash-golf-part-4.html'>2025-09-14 - Bash Golf Part 4</a><br /> diff --git a/uptime-stats.html b/uptime-stats.html index b187e321..1912bedb 100644 --- a/uptime-stats.html +++ b/uptime-stats.html @@ -13,7 +13,7 @@ </p> <h1 style='display: inline' id='my-machine-uptime-stats'>My machine uptime stats</h1><br /> <br /> -<span class='quote'>This site was last updated at 2025-10-31T20:26:55+02:00</span><br /> +<span class='quote'>This site was last updated at 2025-11-01T16:10:35+02:00</span><br /> <br /> <span>The following stats were collected via <span class='inlinecode'>uptimed</span> on all of my personal computers over many years and the output was generated by <span class='inlinecode'>guprecords</span>, the global uptime records stats analyser of mine.</span><br /> <br /> |
