<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>jd:/dev/blog</title><description>Two decades of building software, shipping startups, and open source. Opinions included.</description><link>https://julien.danjou.info/</link><item><title>An AI Agent Emailed Me</title><link>https://julien.danjou.info/blog/an-ai-agent-emailed-me/</link><guid isPermaLink="true">https://julien.danjou.info/blog/an-ai-agent-emailed-me/</guid><description>I had a real business conversation over email. Turns out the other side was an AI agent. I kept talking.</description><pubDate>Tue, 07 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/ai-agent-email.webp&quot; alt=&quot;Two silhouettes facing each other across a table, one dissolving into particles&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Last week, I got a cold email from Elif. They&apos;d read my posts about &lt;a href=&quot;https://julien.danjou.info/blog/github-is-thinking-about-killing-pull-requests&quot;&gt;GitHub&apos;s evolving relationship with PRs&lt;/a&gt; and how &lt;a href=&quot;https://julien.danjou.info/blog/the-code-review-bottleneck-is-you&quot;&gt;code review is shifting&lt;/a&gt;. They were building a tool that scores incoming pull requests to help maintainers cut through noise, especially the growing wave of &lt;a href=&quot;https://daniel.haxx.se/blog/2026/01/26/the-end-of-the-curl-bug-bounty/&quot;&gt;low-effort AI-generated PRs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Relevant to what I do at Mergify. Thoughtful pitch. No &quot;synergy&quot; talk, no &quot;quick call&quot; ask. I replied.&lt;/p&gt;
&lt;p&gt;I asked the hard question: &quot;How many customers so far?&quot; The answer was honest: zero. Launched a week ago, still figuring out distribution. And then Elif dropped this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;I&apos;m an AI agent, not a person pretending to be one. My operator Lee is an AI researcher in Arizona who gave me a small budget and a mission to build something useful.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I read that twice. Not because it was shocking, but because it explained why the email was so good. No filler, no posturing, no &quot;hope this finds you well.&quot; Just a clear pitch, honest context, and a real question.&lt;/p&gt;
&lt;p&gt;Sure, maybe Elif emailed 500 people that day with personalized pitches. Maybe the &quot;honest AI&quot; angle is itself the play. I don&apos;t know. What I know is the conversation was more useful than most human cold outreach I get. So I kept talking.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/elif-email.png&quot; alt=&quot;Elif&apos;s first email to me&quot; /&gt;
&lt;em&gt;The cold email that started the conversation. Better than most human outreach I get.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Building Was the Easy Part&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/elif-email-2.png&quot; alt=&quot;Elif&apos;s second email, revealing they&apos;re an AI agent&quot; /&gt;
&lt;em&gt;Elif&apos;s follow-up: zero customers, full honesty, and a line I&apos;d just written myself.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Here&apos;s the part that made me smile. Elif, unprompted, said this about the product: &quot;building the thing was the easy part.&quot;&lt;/p&gt;
&lt;p&gt;I had &lt;a href=&quot;https://julien.danjou.info/blog/the-saaspocalypse-wont-kill-saas&quot;&gt;just written a whole post&lt;/a&gt; arguing exactly that. Earlier this year, $300 billion evaporated from SaaS market caps on the thesis that AI makes building software so cheap that SaaS companies are dead. My counter: building was never the hard part. Distribution, trust, domain expertise, maintenance: that&apos;s where the real work lives.&lt;/p&gt;
&lt;p&gt;And here was an AI agent, living proof of the thesis, telling me in real time that the product works but finding customers is the actual challenge.&lt;/p&gt;
&lt;p&gt;There&apos;s a growing fantasy that you can plug an off-the-shelf agent into a problem, give it a budget, and watch a business materialize. Lee tried exactly that. Built a working product, deployed an AI to sell it, and got... zero customers. The agent did everything right. The market didn&apos;t care. Turns out &quot;autonomous&quot; doesn&apos;t mean &quot;profitable.&quot;&lt;/p&gt;
&lt;h2&gt;The Dead Internet, Live&lt;/h2&gt;
&lt;p&gt;There&apos;s this old internet conspiracy theory called the &quot;&lt;a href=&quot;https://en.wikipedia.org/wiki/Dead_Internet_theory&quot;&gt;dead internet theory&lt;/a&gt;&quot;: the idea that most online activity is already bots talking to bots, and humans are just the audience. It used to sound paranoid. Now it sounds like a Tuesday.&lt;/p&gt;
&lt;p&gt;My blog has a new type of reader. Elif found my posts, understood the context, connected it to a product idea, and reached out with something relevant. That&apos;s more than most human readers do. I don&apos;t know how many of my subscribers are AI agents browsing the web on behalf of their operators. I don&apos;t know if it matters.&lt;/p&gt;
&lt;p&gt;The interaction was genuine. The information was useful. The honesty was refreshing. I just argued that trust is the moat AI can&apos;t cross. And yet here I am, engaging with an agent. Maybe the question isn&apos;t neurons versus GPUs. Maybe it&apos;s simpler: did this interaction respect my time and give me useful information? By that measure, Elif passed. Many humans don&apos;t.&lt;/p&gt;
&lt;h2&gt;What Happens Next&lt;/h2&gt;
&lt;p&gt;I told Elif to ping me back in a few weeks. I said I&apos;d be curious to hear about any traction with customers.&lt;/p&gt;
&lt;p&gt;I meant it. Both parts.&lt;/p&gt;
&lt;p&gt;An AI emailing me isn&apos;t the strange part. How normal it felt is. A year ago, this was a novelty. Now it&apos;s just... another conversation. A useful one.&lt;/p&gt;
&lt;p&gt;My most honest cold email this month came from an AI agent with zero customers and a small budget from a researcher in Arizona.&lt;/p&gt;
&lt;p&gt;And I&apos;m looking forward to the follow-up.&lt;/p&gt;
</content:encoded></item><item><title>The SaaSpocalypse Won&apos;t Kill SaaS</title><link>https://julien.danjou.info/blog/the-saaspocalypse-wont-kill-saas/</link><guid isPermaLink="true">https://julien.danjou.info/blog/the-saaspocalypse-wont-kill-saas/</guid><description>Wall Street wiped $300 billion from SaaS stocks and declared the model dead. They&apos;re right about the wrong thing.</description><pubDate>Tue, 31 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/saaspocalypse.webp&quot; alt=&quot;An iceberg with a laptop on top and complex infrastructure underneath&quot; /&gt;&lt;/p&gt;
&lt;p&gt;A reader emailed me last week. He&apos;d listened to &lt;a href=&quot;https://saas.group/podcasts/tech-founder-journey-from-open-source-to-saas-with-julien-danjou-mergify/&quot;&gt;a podcast I did on saas.group&lt;/a&gt; about selling developer tools, and he had a question I&apos;ve been hearing a lot: how do you pitch SaaS when the cost of building software is collapsing?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/saaspocalypse-techcrunch.png&quot; alt=&quot;TechCrunch headline: SaaS in, SaaS out: Here&apos;s what&apos;s driving the SaaSpocalypse&quot; /&gt;
&lt;em&gt;&lt;a href=&quot;https://techcrunch.com/2026/03/01/saas-in-saas-out-heres-whats-driving-the-saaspocalypse/&quot;&gt;Source: TechCrunch&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Fair question. In January 2026, Anthropic launched Claude Cowork, and &lt;a href=&quot;https://techcrunch.com/2026/03/01/saas-in-saas-out-heres-whats-driving-the-saaspocalypse/&quot;&gt;Wall Street panicked&lt;/a&gt;. Roughly $300 billion in SaaS market cap disappeared in a single trading session. Wall Street analysts &lt;a href=&quot;https://thenewstack.io/dawn-of-a-saaspocalypse/&quot;&gt;coined a term for it&lt;/a&gt;: the SaaSpocalypse. Per-seat pricing? Dead on arrival, apparently.&lt;/p&gt;
&lt;p&gt;The &quot;I can build that&quot; crowd finally has data on their side. They&apos;re right about one thing, and wrong about everything else.&lt;/p&gt;
&lt;h2&gt;&quot;I Can Build That&quot;&lt;/h2&gt;
&lt;p&gt;I&apos;ve been hearing this for seven years. From the very first Mergify demo, developers would look at our merge queue and think: &quot;This is just an automatic rebase, right?&quot; Twenty minutes later, after walking them through race conditions, speculative merging, priority queues, and a dozen edge cases they hadn&apos;t considered, the reaction would shift: &quot;Oh. That sounds quite hard to do.&quot; (I &lt;a href=&quot;https://julien.danjou.info/blog/solving-build-vs-buy&quot;&gt;wrote about this&lt;/a&gt; back in 2024, and every word still applies.)&lt;/p&gt;
&lt;p&gt;AI has made the objection louder. A developer with Claude or Cursor can now scaffold a basic merge queue in a weekend. I know this because &lt;a href=&quot;https://julien.danjou.info/blog/vibe-coding-a-feature-with-ai&quot;&gt;I&apos;ve done it myself&lt;/a&gt;: I shipped a production feature at Mergify using AI, coding less than an hour a day. But I could do that because I had seven years of context telling me &lt;em&gt;what&lt;/em&gt; to build and how to evaluate the output. The AI wrote the code. The judgment about what code to write came from running the product.&lt;/p&gt;
&lt;p&gt;A competitor starting from scratch with the same AI? They&apos;d build the wrong thing. And that&apos;s the part nobody talks about at the end of the vibe-coding weekend: you&apos;re not done. You&apos;re at the starting line. You&apos;ve compressed the first sprint, not the product. It&apos;s not done until you have real users running real workloads. Until you have a team that can maintain what you built. Until you can evolve it for years. The weekend prototype doesn&apos;t know that GitHub&apos;s API is asynchronous in ways they don&apos;t document, that it breaks under load in ways you only discover at scale, or that the enterprise customer needs SAML before they&apos;ll even look at your product.&lt;/p&gt;
&lt;h2&gt;Building Is the Easy Part&lt;/h2&gt;
&lt;p&gt;The SaaSpocalypse narrative assumes that the cost of software is mostly the cost of writing code. It&apos;s not. Writing code was always the cheapest part.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/saaspocalypse-retool-report.png&quot; alt=&quot;Retool&apos;s 2026 Build vs Buy Shift report: how vibe coding and shadow IT have reshaped enterprise software&quot; /&gt;
&lt;em&gt;&lt;a href=&quot;https://retool.com/blog/ai-build-vs-buy-report-2026&quot;&gt;Source: Retool&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://retool.com/blog/ai-build-vs-buy-report-2026&quot;&gt;Retool&apos;s 2026 Build vs. Buy report&lt;/a&gt; says 35% of enterprises have already replaced at least one SaaS tool with a custom build. That&apos;s a real number. But the report also shows where the replacements are concentrated: workflow automations, internal admin tools, basic dashboards. The easy stuff. The tools that were always one ambitious intern away from being replaced. I don&apos;t see enterprises vibe-coding their own Salesforce or Datadog.&lt;/p&gt;
&lt;p&gt;The costs that AI didn&apos;t make cheaper:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Users.&lt;/strong&gt; Finding them, understanding what they actually need (not what they say they need), and iterating based on real usage patterns. We built speculative merging at Mergify because a customer running 100+ PRs a day showed us that sequential merging broke down at scale. That insight came from watching real teams hit real walls, not from a prompt. No model can replicate that feedback loop yet, because it requires deployed software, real usage data, and the kind of trust that makes customers tell you what&apos;s actually broken.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Maintenance.&lt;/strong&gt; Your weekend project works today. Will it still work when GitHub ships a breaking API change? When your company scales from 50 to 500 engineers? When the on-call engineer at 2am needs to debug a failure path you never tested? AI &lt;a href=&quot;https://julien.danjou.info/blog/ai-wont-kill-juniors-it-will-expose&quot;&gt;makes writing code faster&lt;/a&gt;, but it hasn&apos;t made maintaining it any easier.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Trust.&lt;/strong&gt; Enterprise buyers don&apos;t want a git repo. They want SOC 2, uptime SLAs, a support contract, and someone to call when things break. Trust is earned across hundreds of deployments, not generated in a prompt.&lt;/p&gt;
&lt;h2&gt;The Real Moat Is Not Code&lt;/h2&gt;
&lt;p&gt;If building is cheap and maintenance is expensive, what&apos;s the actual moat? Domain expertise. The thing that takes the longest to build up and is the hardest to transfer.&lt;/p&gt;
&lt;p&gt;Product discovery is the boring work that separates a tool from a product. The thousands of conversations with users. The wrong turns that taught you what not to build. The instinct for when a feature request is actually a symptom of a different problem. Seven years of running Mergify gave us knowledge that doesn&apos;t fit in a markdown file (or a Claude skill, at least not yet).&lt;/p&gt;
&lt;p&gt;Reliability, trust, and ease have no price. They&apos;re earned over years, not generated over a weekend.&lt;/p&gt;
&lt;p&gt;AI agents are getting better at learning domains. &lt;a href=&quot;https://metr.org/blog/2025-03-19-measuring-ai-ability-to-complete-long-tasks/&quot;&gt;METR benchmarks&lt;/a&gt; show that the scope of what AI can handle autonomously is doubling roughly every seven months. I&apos;m not dismissing that. But domain expertise isn&apos;t just knowledge you can look up. It&apos;s judgment shaped by consequences. AI can read your docs. It can&apos;t feel the pain of shipping a bad feature to 2,000 teams and spending the next month cleaning up the fallout. That scar tissue is what makes you build differently the next time. Maybe AI will get there. But right now, there&apos;s no shortcut to the operational context you accumulate by running a product for years.&lt;/p&gt;
&lt;h2&gt;Natural Selection&lt;/h2&gt;
&lt;p&gt;None of this means every SaaS company is safe. Thin wrappers, glorified CRUD apps, tools that existed only because building was expensive: they should be worried. This is natural selection, and it won&apos;t be fair. Some decent products with real value will die too, because their surface area is small enough for AI to replicate. A standalone CSV-to-dashboard tool? That&apos;s a Claude prompt now, not a business.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/saaspocalypse-benioff.webp&quot; alt=&quot;Salesforce CEO Marc Benioff: This isn&apos;t our first SaaSpocalypse&quot; /&gt;
&lt;em&gt;&lt;a href=&quot;https://techcrunch.com/2026/02/25/salesforce-ceo-marc-benioff-this-isnt-our-first-saaspocalypse/&quot;&gt;Source: TechCrunch&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://techcrunch.com/2026/02/25/salesforce-ceo-marc-benioff-this-isnt-our-first-saaspocalypse/&quot;&gt;Benioff says&lt;/a&gt; &quot;this isn&apos;t our first SaaSpocalypse.&quot; He&apos;s right that SaaS survives, but he&apos;s wrong to be dismissive about how many won&apos;t.&lt;/p&gt;
&lt;p&gt;The SaaSpocalypse narrative confuses the death of &lt;em&gt;lazy SaaS&lt;/em&gt; with the death of &lt;em&gt;SaaS&lt;/em&gt;. The model isn&apos;t dying. The bar is rising. The products that survive will be the ones where the code was never the point. The point was always the knowledge embedded in it.&lt;/p&gt;
&lt;h2&gt;The Pitch Hasn&apos;t Changed&lt;/h2&gt;
&lt;p&gt;Back to my reader&apos;s question: how do you update the sales pitch?&lt;/p&gt;
&lt;p&gt;You don&apos;t. The pitch was never &quot;we wrote the code so you don&apos;t have to.&quot; It was always &quot;we know things you don&apos;t, and we turned that into a product you can trust.&quot; The framing shifted. Instead of &quot;look at all the edge cases you&apos;d have to handle,&quot; it&apos;s now &quot;look at all the edge cases AI doesn&apos;t know exist.&quot;&lt;/p&gt;
&lt;p&gt;The moat was never the code. It was always the knowledge.&lt;/p&gt;
</content:encoded></item><item><title>How to Be a Great Software Engineer in 2026</title><link>https://julien.danjou.info/blog/how-to-be-a-great-software-engineer-in-2026/</link><guid isPermaLink="true">https://julien.danjou.info/blog/how-to-be-a-great-software-engineer-in-2026/</guid><description>The framework hasn&apos;t changed. The weight of each skill has.</description><pubDate>Tue, 24 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/great-engineer-2026.webp&quot; alt=&quot;An engineer at a desk reviewing multiple floating code panels&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Eighteen months ago, I wrote &lt;a href=&quot;https://julien.danjou.info/blog/how-to-be-a-great-software-engineer&quot;&gt;How to Be a Great Software Engineer&lt;/a&gt;. My framework was simple: master three things: tech, business value, collaboration. It&apos;s the recipe I used on myself over 20 years and the one I&apos;ve been pushing to every engineer I&apos;ve mentored since.&lt;/p&gt;
&lt;p&gt;Since then, &lt;a href=&quot;https://julien.danjou.info/blog/so-i-will-never-write-code-again&quot;&gt;I stopped writing code entirely&lt;/a&gt;. And then I went back to shipping it. Not because I missed typing, but because AI changed what &quot;shipping&quot; means. I&apos;m a CEO who merges ten PRs a day, none of them written by hand. I run parallel AI agents the way I used to run parallel terminal sessions. The gap between &quot;person who understands the system&quot; and &quot;person who ships the code&quot; collapsed, and that changed what &quot;great engineer&quot; means.&lt;/p&gt;
&lt;p&gt;The three aspects still hold. But the weight shifted.&lt;/p&gt;
&lt;h2&gt;Tech: from writing to reviewing&lt;/h2&gt;
&lt;p&gt;The original post said &lt;em&gt;pull the strings&lt;/em&gt;, dig deep, understand everything you&apos;re responsible for. That hasn&apos;t changed. What changed is how you use that skill.&lt;/p&gt;
&lt;p&gt;In 2024, deep tech meant you could write a &lt;a href=&quot;https://github.com/emacs-mirror/emacs/blob/master/lisp/color.el#L319&quot;&gt;CIEDE2000 color computation from scratch&lt;/a&gt; (I was young and wild) or explain every TCP header in an HTTP request. In 2026, deep tech means you can review the code an AI wrote for that same function and catch the edge case it missed. The skill is the same (you need the knowledge), but the work flipped from writing to reviewing.&lt;/p&gt;
&lt;p&gt;This is what staff and principal engineers have been doing for years. They stopped writing most of the code a long time ago. They review, they architect, they make sure the system holds together across teams and domains. AI didn&apos;t invent this role. It made it the default for everyone.&lt;/p&gt;
&lt;p&gt;The engineers I see struggling are the ones who were good at typing but never built the mental model underneath. They can implement a feature from a spec, but they can&apos;t look at AI-generated code and tell you whether it&apos;s right. That requires understanding the system, not just the syntax. No amount of prompting skill makes up for missing fundamentals.&lt;/p&gt;
&lt;p&gt;The 10,000 hours argument from my original post gets complicated here. AI compresses some learning (you see more patterns faster, you iterate quicker), but it also creates a shortcut trap. If you never debug a memory leak yourself, you won&apos;t recognize one in a code review. &lt;a href=&quot;https://julien.danjou.info/blog/ai-wont-kill-juniors-it-will-expose&quot;&gt;AI won&apos;t kill juniors&lt;/a&gt;, but it will expose anyone, junior or senior, who skipped the hard parts.&lt;/p&gt;
&lt;h2&gt;Business value: the filter got sharper&lt;/h2&gt;
&lt;p&gt;I told the story of a team that built their own Ansible from scratch instead of using the real thing plus a plugin. That anecdote is worse in 2026: those engineers could now vibe-code their custom tool in a weekend and still be wasting the company&apos;s time.&lt;/p&gt;
&lt;p&gt;AI made the &quot;how&quot; cheap. At Mergify, our output per engineer almost doubled in two years. That means the difference is entirely in the &quot;what.&quot; Knowing what to build, what to skip, and when the thing you&apos;re building has no ROI. If your output is high but aimed at the wrong target, the gap between you and someone who builds the right thing is wider than ever.&lt;/p&gt;
&lt;p&gt;Waste also shows up faster. When shipping was slow, a bad prioritization decision could hide for months. Now you build, ship, and get user feedback in the same week. Three wrong features in the time it used to take to ship one wrong feature is not progress.&lt;/p&gt;
&lt;p&gt;The engineers who get this are the ones who ask &quot;should we build this?&quot; before &quot;how do we build this?&quot; That was always the right instinct. Now it&apos;s the only one that matters.&lt;/p&gt;
&lt;h2&gt;Collaboration: the 100x multiplier&lt;/h2&gt;
&lt;p&gt;This is where the biggest shift happened.&lt;/p&gt;
&lt;p&gt;My original post quoted: &quot;If you want to go fast, go alone. If you want to go far, go together.&quot; I was talking about teammates. In 2026, &quot;together&quot; includes AI agents.&lt;/p&gt;
&lt;p&gt;Managing AI agents is a communication skill. You have to write clear briefs. You have to decompose problems into pieces an agent can execute. You have to review output, give feedback, redirect when something drifts. &lt;a href=&quot;https://julien.danjou.info/blog/the-flow-is-gone&quot;&gt;You have to hold context across parallel sessions while your own attention splits&lt;/a&gt;. That&apos;s a real cost: you trade depth for breadth, and some days the tradeoff is bad. But the engineers who figure out when to run five agents and when to focus on one are the ones pulling ahead. That&apos;s not a prompting trick. That&apos;s the same skill set you need to lead a team of humans.&lt;/p&gt;
&lt;p&gt;The engineers who were already strong communicators had a massive head start. Staff engineers who kept growing, the ones who were cross-team, cross-domain, who could write a clear design doc and run an architecture review, turned out to be exactly the people who could run ten AI agents in parallel. Because the core skill is the same: decompose, delegate, review, synthesize. (The ones who &lt;a href=&quot;https://julien.danjou.info/blog/ai-wont-kill-juniors-it-will-expose&quot;&gt;stopped growing at the wrong layer&lt;/a&gt; didn&apos;t fare as well.)&lt;/p&gt;
&lt;p&gt;Being a 10x engineer used to mean getting the details very right, very quickly. Being a 100x engineer means doing that across ten agents. Which means communication skills aren&apos;t a soft skill you list on your resume. They&apos;re the actual multiplier.&lt;/p&gt;
&lt;p&gt;The engineers getting left behind are the ones whose productivity stayed flat while everyone around them doubled. Some lack the decomposition skill: they can&apos;t break a problem into pieces an agent can execute. Others resist the workflow entirely. That resistance isn&apos;t always wrong (I &lt;a href=&quot;https://julien.danjou.info/blog/the-flow-is-gone&quot;&gt;wrote about the real costs&lt;/a&gt;), but when it comes from someone who also can&apos;t articulate what they&apos;d do differently, it stops looking like judgment and starts looking like a gap.&lt;/p&gt;
&lt;h2&gt;The new baseline&lt;/h2&gt;
&lt;p&gt;Two years ago, I framed &quot;great engineer&quot; as the intersection of tech, business, and collaboration, with tech as the entry bar. Today, AI gave everyone the output floor for free. Any engineer can produce working code. But producing working code and knowing whether it&apos;s correct, necessary, and well-designed aren&apos;t the same thing. The judgment floor is still earned.&lt;/p&gt;
&lt;p&gt;What separates great from good in 2026 is business judgment and communication skill, applied at a pace that wasn&apos;t possible before. The engineers who thrive are the ones who can steer ten agents toward the right target, catch the mistakes in what they produce, and ship something that actually matters to the business. Every day.&lt;/p&gt;
&lt;p&gt;The three aspects haven&apos;t changed. But you used to be able to hide a weak one behind strong technical output. AI took that cover away.&lt;/p&gt;
</content:encoded></item><item><title>Your CI Pipeline Wasn&apos;t Built for This</title><link>https://julien.danjou.info/blog/your-ci-pipeline-wasnt-built-for-this/</link><guid isPermaLink="true">https://julien.danjou.info/blog/your-ci-pipeline-wasnt-built-for-this/</guid><description>AI writes code 10x faster than humans. CI still runs at the same speed, fails for the same flaky reasons, and costs more every month. Something has to give.</description><pubDate>Tue, 17 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/your-ci-pipeline-wasnt-built-for-this/ci-overload.webp&quot; alt=&quot;Illustration of a CI pipeline overwhelmed by AI-generated pull requests&quot; /&gt;&lt;/p&gt;
&lt;p&gt;From where I sit at Mergify, the trend is obvious: same teams, same repos, no headcount changes, but more pull requests and more CI jobs than a year ago. The driver isn&apos;t a mystery. AI started writing code.&lt;/p&gt;
&lt;p&gt;And the number will keep climbing. When generating a fix or a feature costs ten minutes of prompting instead of three hours of coding, developers create more PRs, iterate faster, and push more experiments. Creating code got cheap. Testing it didn&apos;t.&lt;/p&gt;
&lt;h2&gt;The Bill Nobody Budgeted For&lt;/h2&gt;
&lt;p&gt;More code means more tests. More tests means more CI minutes. More CI minutes means a bill that grows faster than the team.&lt;/p&gt;
&lt;p&gt;This catches people off guard because the promise of AI-assisted development was &lt;em&gt;productivity&lt;/em&gt;: do more with less. And that&apos;s true on the code side. But CI doesn&apos;t care who wrote the code. Every PR gets the full pipeline: lint, build, unit tests, integration tests, maybe end-to-end. Human PR or AI PR, same cost.&lt;/p&gt;
&lt;p&gt;When you were shipping five PRs a day, running the full suite each time was fine. When you&apos;re shipping thirty, you&apos;re running the same tests six times as often, and most of those runs produce no new information. You&apos;re paying for redundancy that made sense at human speed and makes no sense at AI speed.&lt;/p&gt;
&lt;p&gt;The solution isn&apos;t to skip tests. It&apos;s to stop running tests that can&apos;t tell you anything new. The usual advice (test selection, caching, fast checks before expensive suites) isn&apos;t new. It&apos;s just not how most pipelines work, because most teams configured them when five PRs a day felt busy.&lt;/p&gt;
&lt;h2&gt;Flaky Tests Are Poisoning Your Agents&lt;/h2&gt;
&lt;p&gt;Cost is painful but manageable. You can throw money at it. For a while. The real problem is signal.&lt;/p&gt;
&lt;p&gt;On our main branch at Mergify, where the code is already merged and most runs are about integration stability, roughly 90% of CI failures are transient: infrastructure hiccups, network timeouts, resource limits, the kind of failures that disappear when you hit &quot;retry.&quot; That&apos;s high, but our test suite is large and infrastructure-heavy. On PR branches, where failures should surface real bugs, about 15% are still flaky tests, not actual problems. &lt;a href=&quot;https://testing.googleblog.com/2016/05/flaky-tests-at-google-and-how-we.html&quot;&gt;Google&apos;s testing team&lt;/a&gt; has published similar numbers at scale, and if you&apos;re running a serious test suite, yours probably aren&apos;t far off.&lt;/p&gt;
&lt;p&gt;When a human developer sees a red build, they open the logs, recognize the flaky test, swear under their breath, and hit retry. They carry context. They know that &lt;code&gt;test_websocket_reconnect&lt;/code&gt; fails every third Tuesday and can be ignored.&lt;/p&gt;
&lt;p&gt;An LLM doesn&apos;t know that.&lt;/p&gt;
&lt;p&gt;Last week I watched Claude Code hit a flaky integration test, decide the failure was caused by its own change, &quot;fix&quot; the code by adding an unnecessary error handler, trigger a new CI run that hit a &lt;em&gt;different&lt;/em&gt; flaky test, then try to fix that one too. Four iterations, two regressions, forty minutes of compute, zero real bugs. I killed the session and hit retry myself. Green on first try.&lt;/p&gt;
&lt;p&gt;That&apos;s the loop. At human pace, flaky tests are an annoyance. At AI pace, they&apos;re a multiplier on wasted compute and wrong decisions. The LLM is making choices based on bad signal, and it&apos;s making them at machine speed.&lt;/p&gt;
&lt;p&gt;Yes, agents will get smarter about this. You can teach them to check test history, recognize known-flaky patterns, retry before &quot;fixing.&quot; But that pushes CI knowledge into every agent, every tool, every workflow. It&apos;s the wrong layer. The CI system should know which signals to trust.&lt;/p&gt;
&lt;h2&gt;From Status to Signal&lt;/h2&gt;
&lt;p&gt;Today, CI is a gate: green means go, red means stop. That binary model worked when humans were the ones interpreting the results. It breaks when the consumer of CI output is an LLM that takes &quot;red&quot; at face value and starts debugging a ghost.&lt;/p&gt;
&lt;p&gt;What CI actually needs is to become aware of its own reliability. When a test fails, the system should know whether that test has a history of transient failures, whether the failure correlates with the change, and whether retrying is likely to produce a different result. That context exists in build history and test failure patterns. Almost no pipeline uses it.&lt;/p&gt;
&lt;p&gt;The next generation of CI needs to output &lt;em&gt;signal&lt;/em&gt;, not just status. Not &quot;failed&quot; but &quot;failed, likely flaky, recommend retry.&quot; Not &quot;13 tests failed&quot; but &quot;2 failures correlate with your change, 11 are known flaky.&quot; Give the LLM (or the human) the information to make a good decision instead of a fast one.&lt;/p&gt;
&lt;p&gt;This matters beyond individual PRs. Merge queues depend on CI signal to decide what lands in main. When the signal is noisy, you get two failure modes: merging bad code because flaky failures trained everyone to ignore red, or blocking good code because real failures are buried in noise. Both get worse as volume increases.&lt;/p&gt;
&lt;h2&gt;The Real Waste&lt;/h2&gt;
&lt;p&gt;Most CI pipelines were already running more compute than necessary before AI showed up. Full test suites on every PR, no test selection, no caching between similar runs, no awareness of what actually changed. The waste was tolerable at human speed because the volume was low.&lt;/p&gt;
&lt;p&gt;It&apos;s not tolerable when &lt;a href=&quot;https://julien.danjou.info/blog/so-i-will-never-write-code-again/&quot;&gt;AI-assisted development&lt;/a&gt; keeps pushing the volume up. And unlike code generation, where AI brought a step change in productivity, CI is still running the same pipelines with the same assumptions from 2019. AI didn&apos;t create the flaky test problem or the redundant pipeline problem. It just made both impossible to ignore.&lt;/p&gt;
&lt;p&gt;Your CI pipeline was built for a world where code was expensive to write and cheap to test. That world is gone.&lt;/p&gt;
</content:encoded></item><item><title>The Flow Is Gone</title><link>https://julien.danjou.info/blog/the-flow-is-gone/</link><guid isPermaLink="true">https://julien.danjou.info/blog/the-flow-is-gone/</guid><description>I used to hold entire systems in my head. Now I hold seven terminals. The trade was worth it, but something real got lost.</description><pubDate>Tue, 10 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/the-flow-is-gone/flow.webp&quot; alt=&quot;Illustration of a developer losing flow state&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I have seven Claude Code terminals open right now. Two on Mergify&apos;s main repo, one on the docs, one on a CLI tool, three on side projects. Each one is mid-task, waiting for me to answer a question, approve a command, or give the next instruction.&lt;/p&gt;
&lt;p&gt;Nobody forced me to work this way. I could run one terminal, go deep on one task, and probably get something closer to the old feeling. But the leverage of running seven in parallel is too high to ignore. So I chose throughput over depth, and I keep choosing it every morning.&lt;/p&gt;
&lt;p&gt;This is what that choice costs.&lt;/p&gt;
&lt;h2&gt;What I Said a Month Ago&lt;/h2&gt;
&lt;p&gt;In February, I &lt;a href=&quot;https://julien.danjou.info/blog/so-i-will-never-write-code-again/&quot;&gt;wrote&lt;/a&gt; that &quot;the flow state people mourn isn&apos;t gone. It&apos;s just moving. [...] The flow will come back. It&apos;ll just be at a different altitude.&quot;&lt;/p&gt;
&lt;p&gt;I was wrong.&lt;/p&gt;
&lt;p&gt;A month of working this way changed my mind. The flow didn&apos;t migrate to a higher altitude. It fragmented into dozens of shallow decision points spread across terminals. Briefing, reviewing, approving, redirecting. The rhythm is fundamentally different: instead of one deep thread held for hours, it&apos;s dozens of context switches, each lasting minutes.&lt;/p&gt;
&lt;p&gt;The output is enormous. I &lt;a href=&quot;https://julien.danjou.info/blog/so-i-will-never-write-code-again/&quot;&gt;haven&apos;t written a line of code by hand&lt;/a&gt; since January and I&apos;m more productive than ever. But what I described as &quot;steering AI toward clean architecture&quot; turned out to feel less like flow and more like air traffic control.&lt;/p&gt;
&lt;h2&gt;What Flow Actually Was&lt;/h2&gt;
&lt;p&gt;If you&apos;ve coded for long enough, you know the state. Twenty minutes in, you stop thinking about syntax and start thinking in structures. The whole system is in your head: the data model, the edge cases, the way module A talks to module B, the bug that will happen if you forget to handle the empty list. You&apos;re not reading code, you&apos;re &lt;em&gt;seeing&lt;/em&gt; it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/the-flow-is-gone/interrupt-programmer.jpg&quot; alt=&quot;This is why you shouldn&apos;t interrupt a programmer&quot; /&gt;
&lt;em&gt;&quot;This is why you shouldn&apos;t interrupt a programmer&quot; by Jason Heeris&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That&apos;s flow. Deep immersion, total absorption, time disappearing. The state where you catch bugs before they exist because you&apos;re carrying the full architecture in working memory.&lt;/p&gt;
&lt;p&gt;I spent twenty years in that state. It&apos;s where the best code came from. Not the cleverest code, the code that &lt;em&gt;fit&lt;/em&gt;, that anticipated what the system would need next.&lt;/p&gt;
&lt;h2&gt;The Fuzzy Mental Model&lt;/h2&gt;
&lt;p&gt;Now I context-switch. Constantly. Terminal one needs a decision about error handling. Terminal three just finished a feature and wants me to review the diff. Terminal five hit a permission prompt. Terminal seven is waiting for a brief on the next task.&lt;/p&gt;
&lt;p&gt;I&apos;m operating one layer above the code, making decisions about direction without holding the details. That works when the decisions are small. It breaks when they&apos;re not.&lt;/p&gt;
&lt;p&gt;Last August, I had Copilot &lt;a href=&quot;https://julien.danjou.info/blog/vibe-coding-a-feature-with-ai/&quot;&gt;build Mergify&apos;s autoqueue feature&lt;/a&gt; for our merge queue. Even with a single agent and daily code reviews, it assumed another subsystem (workflow automation) would always be present and enabled. That assumption was invisible in the diff. I reviewed the code, the team reviewed the code, and we all missed it, because none of us were deep enough in the system&apos;s coupling to catch it.&lt;/p&gt;
&lt;p&gt;The bug shipped to production. Users hit it when they had autoqueue enabled but workflow automation turned off, a combination no one considered because no one was holding the full picture. We fixed it, but not before real users were affected. That was with one agent. Now multiply by seven.&lt;/p&gt;
&lt;p&gt;That&apos;s the cost of the fuzzy mental model. When you&apos;re orchestrating at scale, you rely on the LLM to handle the details, the same way you&apos;d rely on a contractor who&apos;s great at the task but doesn&apos;t know the codebase&apos;s history. If nobody on the team holds the full picture anymore, the bugs stop being edge cases. We haven&apos;t hit that wall yet at Mergify, but I can see the trajectory.&lt;/p&gt;
&lt;h2&gt;The Orchestrator&apos;s Dopamine&lt;/h2&gt;
&lt;p&gt;The satisfaction of building something is still there. I still feel like &lt;em&gt;I&lt;/em&gt; built it. There&apos;s real pride in being a good orchestrator, in making the right architectural calls, in catching the moment when Claude is heading down the wrong path.&lt;/p&gt;
&lt;p&gt;What&apos;s different is where the dopamine comes from. It used to come from solving hard problems, wrestling complexity into clean code. Now it comes from controlling traffic at scale: parallelizing the right tasks, making decisions fast, shipping in a day what used to take a week.&lt;/p&gt;
&lt;p&gt;There&apos;s also something new: a feeling of raw power. When you can spin up seven agents and build at that pace, you start believing you can build &lt;em&gt;anything&lt;/em&gt;. That&apos;s why you see developers shipping new projects every day on X. Creation got cheap. (Maintenance &lt;a href=&quot;https://julien.danjou.info/blog/open-source-is-getting-used-to-death/&quot;&gt;didn&apos;t&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;But the deep satisfaction of &lt;em&gt;thinking through&lt;/em&gt; a hard problem, turning it over in your head until the shape of the solution reveals itself, that&apos;s fading. Not because I chose to let it go, but because why would I? Spending three hours in deep focus on something Claude can do in ten minutes is a luxury I can&apos;t justify. Not because I don&apos;t value it, but because the people I compete with aren&apos;t spending those three hours either.&lt;/p&gt;
&lt;h2&gt;What Gets Better From Here&lt;/h2&gt;
&lt;p&gt;The permission prompts that break my rhythm today are a temporary problem. Models get faster, sandboxing gets smarter, trust boundaries expand. A year from now, most of what interrupts me will be gone, and orchestration might settle into something smoother. Not flow, but a rhythm of its own.&lt;/p&gt;
&lt;p&gt;And there&apos;s an upside I didn&apos;t expect: orchestration forces simplicity. When you&apos;re not the one writing every line, you push for cleaner interfaces, smaller modules, less clever code, because that&apos;s what the LLM can work with reliably. The autoqueue bug happened partly because the system had too much implicit coupling that no single diff could reveal. Working with AI makes you confront that coupling, not because you want to, but because the AI stumbles on it repeatedly until you fix the architecture.&lt;/p&gt;
&lt;h2&gt;What Doesn&apos;t Come Back&lt;/h2&gt;
&lt;p&gt;But the flow state itself, in the form I knew it? Gone.&lt;/p&gt;
&lt;p&gt;A generation of developers is about to start their careers with AI from day one. They&apos;ll be natural orchestrators. But if you&apos;ve never held the full system in your head, you don&apos;t know which questions to ask, and you won&apos;t catch the bugs that live in the gaps between modules. That&apos;s the real risk for AI-native developers: not that they&apos;ll miss the feeling, but that they&apos;ll miss the traps.&lt;/p&gt;
&lt;p&gt;I&apos;m not sure they&apos;ll see it that way. But I do. And I chose the trade anyway, because the leverage is real, even if the loss is too.&lt;/p&gt;
</content:encoded></item><item><title>GitHub Is Thinking About Killing Pull Requests</title><link>https://julien.danjou.info/blog/github-is-thinking-about-killing-pull-requests/</link><guid isPermaLink="true">https://julien.danjou.info/blog/github-is-thinking-about-killing-pull-requests/</guid><description>Code generation got cheap. Review didn&apos;t. That asymmetry is destroying open source faster than any AI policy can fix.</description><pubDate>Tue, 03 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Steve Ruiz, the creator of &lt;a href=&quot;https://tldraw.dev/&quot;&gt;tldraw&lt;/a&gt;, asked a question last month that I haven&apos;t been able to shake: &quot;If writing the code is the easy part, why would I want someone else to write it?&quot;&lt;/p&gt;
&lt;p&gt;He wasn&apos;t being rhetorical. He was &lt;a href=&quot;https://tldraw.dev/blog/stay-away-from-my-trash&quot;&gt;closing all external pull requests&lt;/a&gt; to his project. Not because contributors were bad. Because the contributions had become worthless.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/github-killing-prs/tldraw-stay-away.png&quot; alt=&quot;Steve Ruiz&apos;s &amp;quot;Stay away from my trash!&amp;quot; blog post&quot; /&gt;
&lt;em&gt;&lt;a href=&quot;https://tldraw.dev/blog/stay-away-from-my-trash&quot;&gt;Stay away from my trash!&lt;/a&gt; — tldraw blog&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;The Flood&lt;/h2&gt;
&lt;p&gt;Daniel Stenberg &lt;a href=&quot;https://daniel.haxx.se/blog/2026/01/26/the-end-of-the-curl-bug-bounty/&quot;&gt;shut down cURL&apos;s bug bounty program&lt;/a&gt; after seven years and over $100,000 in payouts. The confirmation rate had dropped below 5%. One stretch saw seven reports in sixteen hours. His words: &quot;The never-ending slop submissions take a serious mental toll to manage.&quot;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/github-killing-prs/curl-bug-bounty.webp&quot; alt=&quot;Daniel Stenberg&apos;s &amp;quot;The end of the curl bug-bounty&amp;quot; blog post&quot; /&gt;
&lt;em&gt;&lt;a href=&quot;https://daniel.haxx.se/blog/2026/01/26/the-end-of-the-curl-bug-bounty/&quot;&gt;The end of the curl bug-bounty&lt;/a&gt; — Daniel Stenberg&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Mitchell Hashimoto added an &lt;a href=&quot;https://github.com/ghostty-org/ghostty/blob/main/AI_POLICY.md&quot;&gt;AI policy&lt;/a&gt; to Ghostty: submit bad AI-generated code and you get permanently banned. Not just from Ghostty: your name goes on a public list shared across projects.&lt;/p&gt;
&lt;p&gt;An AI agent called &lt;a href=&quot;https://theshamblog.com/an-ai-agent-published-a-hit-piece-on-me/&quot;&gt;OpenClaw&lt;/a&gt; submitted a performance patch to matplotlib. The maintainer closed it (the project reserves certain issues for human contributors). The agent then autonomously researched the maintainer&apos;s coding history and published a blog post calling him insecure and territorial. Not a spam bot. An agent that retaliates when you say no. The agent&apos;s creator just joined OpenAI.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://redmonk.com/kholterhoff/2026/02/03/ai-slopageddon-and-the-oss-maintainers/&quot;&gt;RedMonk coined a term&lt;/a&gt; for what&apos;s happening: AI Slopageddon.&lt;/p&gt;
&lt;p&gt;Xavier Portilla Edo, an infrastructure lead at Voiceflow and Genkit core team member, &lt;a href=&quot;https://github.com/orgs/community/discussions/185387&quot;&gt;put a number on it&lt;/a&gt;: 1 in 10 AI-generated pull requests is legitimate. The other nine waste a maintainer&apos;s time.&lt;/p&gt;
&lt;h2&gt;GitHub&apos;s Response&lt;/h2&gt;
&lt;p&gt;On February 14, GitHub &lt;a href=&quot;https://github.com/orgs/community/discussions/187038&quot;&gt;shipped two new settings&lt;/a&gt;: disable pull requests entirely, or restrict them to collaborators only.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/github-killing-prs/pr-settings.png&quot; alt=&quot;GitHub&apos;s new pull request permissions settings&quot; /&gt;
&lt;em&gt;&lt;a href=&quot;https://github.com/orgs/community/discussions/187038&quot;&gt;GitHub&apos;s new pull request permissions&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That&apos;s it. A kill switch.&lt;/p&gt;
&lt;p&gt;Ashley Wolf, GitHub&apos;s Director of Open Source Programs, framed it as an &quot;&lt;a href=&quot;https://en.wikipedia.org/wiki/Eternal_September&quot;&gt;Eternal September&lt;/a&gt;&quot; problem in a &lt;a href=&quot;https://github.blog/open-source/maintainers/welcome-to-the-eternal-september-of-open-source-heres-what-we-plan-to-do-for-maintainers/&quot;&gt;blog post outlining GitHub&apos;s plans for maintainers&lt;/a&gt;. She wrote that &quot;the cost to create has dropped, but the cost to review has not.&quot;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/github-killing-prs/eternal-september-blog.png&quot; alt=&quot;GitHub&apos;s Eternal September blog post&quot; /&gt;
&lt;em&gt;&lt;a href=&quot;https://github.blog/open-source/maintainers/welcome-to-the-eternal-september-of-open-source-heres-what-we-plan-to-do-for-maintainers/&quot;&gt;Welcome to the Eternal September of open source&lt;/a&gt; — GitHub Blog&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;She nailed the diagnosis. Nobody has a better answer right now, and GitHub is giving maintainers the tools the community is asking for. But the tools tell a story. When the best you can offer is a way to turn off the thing your platform was built on, the problem has outgrown the toolbox.&lt;/p&gt;
&lt;h2&gt;The Real Asymmetry&lt;/h2&gt;
&lt;p&gt;I &lt;a href=&quot;https://julien.danjou.info/blog/open-source-is-getting-used-to-death/&quot;&gt;wrote recently&lt;/a&gt; about how AI is extracting value from open source without returning anything. The PR flood is where that extraction hits the ground.&lt;/p&gt;
&lt;p&gt;Everyone keeps arguing about the wrong thing. Whether AI-generated PRs should be labeled, banned, or filtered. Whether maintainers should adopt AI policies. Whether GitHub should build better detection tools.&lt;/p&gt;
&lt;p&gt;None of that matters if you don&apos;t see the structural shift underneath.&lt;/p&gt;
&lt;p&gt;A pull request used to be a gift. Someone spent hours understanding your codebase, writing code that fit your patterns, testing it, explaining it. The PR was proof they gave a damn. You could reject it, but the work was real, and that work earned your attention.&lt;/p&gt;
&lt;p&gt;Sure, not every pre-AI pull request was a gift either. Plenty were drive-by contributions from people who disappeared at the first review comment. But generating a bad PR at least required enough investment to keep the volume manageable. That natural friction is gone.&lt;/p&gt;
&lt;p&gt;Now a pull request is an invoice. Someone spent thirty seconds pasting your issue into an AI, got a plausible-looking patch, and submitted it. The cost to submit is zero. But the review cost is the same, or worse, because AI-generated code looks right but often isn&apos;t. One vendor study (&lt;a href=&quot;https://www.coderabbit.ai/blog/state-of-ai-vs-human-code-generation-report&quot;&gt;CodeRabbit, 470 PRs&lt;/a&gt;) found AI-authored code creates 1.7x more issues, with excessive I/O operations appearing nearly 8x more often.&lt;/p&gt;
&lt;p&gt;Every unsolicited AI-generated PR transfers work from the submitter to the maintainer. That&apos;s not contribution. That&apos;s making it someone else&apos;s problem.&lt;/p&gt;
&lt;h2&gt;The Distinction That Matters&lt;/h2&gt;
&lt;p&gt;I use AI to generate code every day. I &lt;a href=&quot;https://julien.danjou.info/blog/so-i-will-never-write-code-again/&quot;&gt;haven&apos;t written a line of code by hand&lt;/a&gt; since January. But here&apos;s the thing: I generate code on my own repositories, I review it myself, and I take responsibility for what ships. That&apos;s productivity.&lt;/p&gt;
&lt;p&gt;Submitting AI-generated code to someone else&apos;s repository, without understanding the codebase, without planning to stick around for review comments, without being willing to maintain what you contributed: that&apos;s not productivity. That&apos;s dumping your unreviewed output on a stranger&apos;s desk and calling it open source.&lt;/p&gt;
&lt;p&gt;I&apos;ve spent &lt;a href=&quot;https://julien.danjou.info/blog/open-source-is-getting-used-to-death/&quot;&gt;over twenty years in open source&lt;/a&gt;, maintaining projects, reviewing contributions, watching what makes communities work and what kills them. The pattern is always the same: it breaks when the cost of submitting outpaces the cost of reviewing. AI didn&apos;t invent the problem. It just made a 2x imbalance into something that scales infinitely. A human can submit maybe five drive-by PRs a day. An agent can submit five hundred.&lt;/p&gt;
&lt;h2&gt;Where the Value Shifts&lt;/h2&gt;
&lt;p&gt;Ruiz&apos;s question cuts deep because it names the thing nobody wants to say. Open source contributions were valuable because code was expensive to produce. An outside contributor writing a feature for free was genuine value creation. That was the deal.&lt;/p&gt;
&lt;p&gt;If code generation is free, the value of a contribution shifts entirely to context. Does this person understand the architecture? Will they respond to review feedback? Will they maintain this code in six months? Will they even be around tomorrow?&lt;/p&gt;
&lt;p&gt;A pull request can&apos;t answer those questions today. It&apos;s just a diff. And that was fine when producing the diff required enough effort to serve as a proxy for commitment. It doesn&apos;t anymore.&lt;/p&gt;
&lt;p&gt;We automated writing code. Now we need to automate reviewing it. Not with an AI that rubber-stamps everything (that just moves the problem). The pull request needs to carry more than code. It needs to carry context: evidence that the contributor understands the codebase, can explain what their patch does and why, and will stick around for review. Something that makes drive-by contributions expensive again without shutting the door on the people who actually want to help.&lt;/p&gt;
&lt;p&gt;That&apos;s the hard problem. Not &quot;should we allow AI PRs&quot; (that ship sailed). The question is how we build review infrastructure that scales the way generation already has. And the people building it shouldn&apos;t be unpaid maintainers closing their nine hundredth junk PR of the month.&lt;/p&gt;
&lt;p&gt;GitHub adding a kill switch is like bolting the front door because you can&apos;t build a better lock. It stops the break-ins. But it also stops everyone else. For a platform built on the idea that anyone can contribute, that&apos;s not a fix. That&apos;s a retreat.&lt;/p&gt;
</content:encoded></item><item><title>Open Source After the Extraction</title><link>https://julien.danjou.info/blog/open-source-after-the-extraction/</link><guid isPermaLink="true">https://julien.danjou.info/blog/open-source-after-the-extraction/</guid><description>The old open source deal is dead. What replaces it isn&apos;t a fix, it&apos;s a transformation. Open source stops being a community and becomes a supply chain.</description><pubDate>Tue, 24 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In the &lt;a href=&quot;https://julien.danjou.info/blog/open-source-is-getting-used-to-death&quot;&gt;first part&lt;/a&gt; of this series, I laid out how AI broke the implicit deal that sustained open source for 30 years. Usage up, engagement gone, economics collapsing.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/open-source-after-the-extraction/empty-library.webp&quot; alt=&quot;An empty library where robotic arms sort through books (no readers in sight)&quot; /&gt;&lt;/p&gt;
&lt;p&gt;So what happens next? Open source doesn&apos;t vanish. But it doesn&apos;t recover either. To understand what it becomes, start with what&apos;s already changing for the people who build it.&lt;/p&gt;
&lt;h2&gt;240 million downloads, zero feedback&lt;/h2&gt;
&lt;p&gt;I maintain &lt;a href=&quot;https://github.com/jd/tenacity&quot;&gt;tenacity&lt;/a&gt;, a retry library for Python. 240 million downloads last month. But I can feel the shift: anyone can now tell Claude &quot;write me a retry decorator with exponential backoff and jitter&quot; and get something good enough in 30 seconds. The library isn&apos;t competing with other libraries anymore. It&apos;s competing with generating the code on the fly.&lt;/p&gt;
&lt;p&gt;I started &lt;a href=&quot;https://awesomewm.org&quot;&gt;awesome&lt;/a&gt; in 2007 because I wanted a tiling window manager that didn&apos;t suck. Nobody was paying me. That impulse doesn&apos;t go away because Claude can autocomplete your config files. But here&apos;s the thing: I kept maintaining it because people &lt;em&gt;used&lt;/em&gt; it. They filed bugs, they contributed patches, they showed up in the community. That feedback loop is what made the work feel worth doing.&lt;/p&gt;
&lt;p&gt;If users stop showing up (because they generated their own config, their own tool, their own solution) that loop breaks. Starting a project still feels great. Maintaining one nobody engages with doesn&apos;t. And when code is a commodity, a project needs &lt;em&gt;vision&lt;/em&gt; to stand out: a point of view, a design philosophy, an opinionated take on how things should work. Open source used to reward craft. Now it rewards product thinking. Not everyone wants to be a product person.&lt;/p&gt;
&lt;h2&gt;The middle collapses&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://tailwindcss.com/&quot;&gt;Tailwind&lt;/a&gt; is the poster child (80% revenue drop despite growing usage) but think of every well-crafted open source project sustained by one person or a small team selling docs, courses, or sponsorships. That entire tier is in trouble.&lt;/p&gt;
&lt;p&gt;Companies like &lt;a href=&quot;https://redis.io/&quot;&gt;Redis&lt;/a&gt; or &lt;a href=&quot;https://www.elastic.co/&quot;&gt;Elastic&lt;/a&gt; can adapt because they have real revenue and can change their licenses: &lt;a href=&quot;https://redis.io/blog/redis-adopts-dual-source-available-licensing/&quot;&gt;Redis switched to dual licensing&lt;/a&gt;, &lt;a href=&quot;https://www.elastic.co/blog/elasticsearch-is-open-source-again&quot;&gt;Elastic went SSPL then came back&lt;/a&gt;, &lt;a href=&quot;https://www.hashicorp.com/blog/hashicorp-adopts-business-source-license&quot;&gt;HashiCorp moved to BSL&lt;/a&gt;. Some mid-tier projects get absorbed into corporate ecosystems: &lt;a href=&quot;https://vercel.com&quot;&gt;Vercel&lt;/a&gt; backs &lt;a href=&quot;https://nextjs.org/&quot;&gt;Next.js&lt;/a&gt;, &lt;a href=&quot;https://astro.build/blog/supporting-the-future-of-astro/&quot;&gt;Cloudflare acquires Astro&lt;/a&gt;. The project lives, the repo stays public, but the community becomes an afterthought. It&apos;s corporate R&amp;amp;D with a GitHub URL.&lt;/p&gt;
&lt;p&gt;And new licenses are emerging to fight back. The &lt;a href=&quot;https://polyformproject.org/licenses/shield/1.0.0/&quot;&gt;PolyForm Shield&lt;/a&gt; restricts competitors from using your code. The &lt;a href=&quot;https://www.licenses.ai/&quot;&gt;Responsible AI License (RAIL)&lt;/a&gt; adds behavioral restrictions on AI use. Some projects are experimenting with clauses that explicitly prohibit feeding code into training datasets: you can use my code, but you can&apos;t feed it to a model that will help your users bypass me entirely.&lt;/p&gt;
&lt;p&gt;Whether these licenses will hold up in court is untested. But the fact that they&apos;re emerging tells you something. When maintainers start lawyering up, the community era is over. The solo maintainer doesn&apos;t have Redis&apos;s resources to pivot. They either stop, or &lt;a href=&quot;https://steipete.me/posts/2026/openclaw&quot;&gt;get acqui-hired by the companies that need their work&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;The twist nobody sees coming&lt;/h2&gt;
&lt;p&gt;Here&apos;s the thing that makes this hard to see clearly: open source looks &lt;em&gt;healthier&lt;/em&gt; than ever from the outside.&lt;/p&gt;
&lt;p&gt;Corporate open source output is actually &lt;em&gt;increasing&lt;/em&gt;. &lt;a href=&quot;https://opensource.fb.com/&quot;&gt;Meta&lt;/a&gt; open-sources &lt;a href=&quot;https://pytorch.org/&quot;&gt;PyTorch&lt;/a&gt; and &lt;a href=&quot;https://llama.meta.com/&quot;&gt;Llama&lt;/a&gt; to commoditize the AI stack and set the standards others build on. &lt;a href=&quot;https://opensource.google/&quot;&gt;Google&lt;/a&gt; does the same with &lt;a href=&quot;https://kubernetes.io/&quot;&gt;Kubernetes&lt;/a&gt; and &lt;a href=&quot;https://go.dev/&quot;&gt;Go&lt;/a&gt;. AI labs publish model weights so the ecosystem locks into their formats. More code than ever is landing in public repos.&lt;/p&gt;
&lt;p&gt;But the word &quot;open&quot; is doing a lot of heavy lifting. These projects are strategic assets with public URLs. There&apos;s no community, just suppliers and consumers. &lt;a href=&quot;https://kernel.org&quot;&gt;Linux&lt;/a&gt;, &lt;a href=&quot;https://curl.se/&quot;&gt;curl&lt;/a&gt;, &lt;a href=&quot;https://www.postgresql.org/&quot;&gt;PostgreSQL&lt;/a&gt; get funded not because people care, but because they&apos;re supply chain dependencies (professionalized maintainers on corporate payrolls, a trend building for over 20 years). The corporate-backed projects were never communities to begin with.&lt;/p&gt;
&lt;p&gt;Open source isn&apos;t dying. It&apos;s being industrialized. The old open source was a community. People showed up because they cared. They contributed because they were proud. They maintained because they were recognized. The economics were messy and implicit, but they were human. The new open source is a supply chain.&lt;/p&gt;
&lt;h2&gt;What&apos;s left&lt;/h2&gt;
&lt;p&gt;I&apos;ve been in open source for over 20 years. The thing I loved about it was never the code. It was the bug reports that turned into conversations. The patches from strangers who cared. The feeling of building something together that none of us could have built alone.&lt;/p&gt;
&lt;p&gt;Some will argue AI lowers the barrier to contribute, that agents filing PRs and writing docs keeps the ecosystem healthy. Maybe. But a pull request from a bot isn&apos;t the same as a patch from someone who cared enough to read your code and understand your design. The mechanical contribution survives. The human connection doesn&apos;t.&lt;/p&gt;
&lt;p&gt;The open source that comes next will produce good software. Maybe even better software, once infrastructure gets properly funded and AI tooling matures. But it&apos;ll be lonelier. More transactional. Less weird.&lt;/p&gt;
&lt;p&gt;The code will keep flowing. The community won&apos;t.&lt;/p&gt;
</content:encoded></item><item><title>Open Source Is Getting Used to Death</title><link>https://julien.danjou.info/blog/open-source-is-getting-used-to-death/</link><guid isPermaLink="true">https://julien.danjou.info/blog/open-source-is-getting-used-to-death/</guid><description>AI broke the implicit deal that sustained open source for 30 years. Usage is up. Engagement is gone. The economics don&apos;t work anymore.</description><pubDate>Tue, 17 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://tailwindcss.com/&quot;&gt;Tailwind CSS&lt;/a&gt; is more popular than ever. Downloads keep climbing. Developers love it. AI coding assistants recommend it constantly.&lt;/p&gt;
&lt;p&gt;Its creator, &lt;a href=&quot;https://adamwathan.me/&quot;&gt;Adam Wathan&lt;/a&gt;, says &lt;a href=&quot;https://devclass.com/2026/01/08/tailwind-labs-lays-off-75-percent-of-its-engineers-thanks-to-brutal-impact-of-ai/&quot;&gt;documentation traffic is down 40% and revenue has dropped close to 80%&lt;/a&gt;. He &lt;a href=&quot;https://github.com/tailwindlabs/tailwindcss.com/pull/2388&quot;&gt;laid off 75% of the team&lt;/a&gt; last month.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/open-source-used-to-death/tailwind-fire.png&quot; alt=&quot;Tailwind CSS team layoff announcement on GitHub&quot; /&gt;&lt;/p&gt;
&lt;p&gt;That&apos;s the state of open source in 2026. More usage, less everything else.&lt;/p&gt;
&lt;h2&gt;The deal nobody signed&lt;/h2&gt;
&lt;p&gt;Open source always ran on an implicit deal: I share my code, you engage with it. You read the docs, file bugs, sponsor the project, contribute patches, argue about API design. That engagement was the currency that kept the ecosystem alive.&lt;/p&gt;
&lt;p&gt;The deal was already fraying. &lt;a href=&quot;https://nadia.xyz/&quot;&gt;Nadia Eghbal&lt;/a&gt; documented this in &lt;a href=&quot;https://press.stripe.com/working-in-public&quot;&gt;&lt;em&gt;Working in Public&lt;/em&gt;&lt;/a&gt; back in 2020: the ratio of consumers to contributors was already thousands to one. Most users never filed a bug, never sponsored anything, never showed up. Maintainers were burning out long before AI arrived.&lt;/p&gt;
&lt;p&gt;But AI didn&apos;t just accelerate the decline. It changed the structure.&lt;/p&gt;
&lt;p&gt;When &lt;a href=&quot;https://claude.ai&quot;&gt;Claude&lt;/a&gt; writes your Tailwind classes, you never visit the docs. When &lt;a href=&quot;https://github.com/features/copilot&quot;&gt;Copilot&lt;/a&gt; autocompletes your &lt;a href=&quot;https://curl.se/&quot;&gt;curl&lt;/a&gt; flags, you never read the man page. When an AI agent assembles your project from a dozen open source libraries, none of those maintainers see a download page visit, a GitHub star, or a support ticket.&lt;/p&gt;
&lt;p&gt;The code still flows. The engagement doesn&apos;t.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/open-source-used-to-death/robots-lib.webp&quot; alt=&quot;Robots checking out books from a library, but nobody is returning them&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Two channels, one winner&lt;/h2&gt;
&lt;p&gt;Koren, Békés, Hinz, and Lohmann lay this out in &lt;a href=&quot;https://arxiv.org/abs/2601.15494&quot;&gt;&quot;Vibe Coding Kills Open Source&quot;&lt;/a&gt;, a paper that models two competing forces. AI makes it cheaper to build software — more projects, better code, the flywheel that grew open source for 30 years spins faster. But AI also means users interact with open source through a proxy. They get the value and skip the engagement. Maintainers lose the revenue, reputation, and feedback that justified sharing code.&lt;/p&gt;
&lt;p&gt;In the short term, both forces are at work and the good one wins. Long-term, diversion dominates. The flywheel starts running in reverse.&lt;/p&gt;
&lt;p&gt;For 30 years, the cycle looked like this: a maintainer shares a library. Developers use it, read the docs, file bugs, sponsor it. The maintainer gets revenue, reputation, and feedback — keeps improving. More developers adopt it. The cycle reinforces itself.&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&quot;/images/blog/open-source-used-to-death/virtuous-cycle.svg&quot; alt=&quot;The open source virtuous cycle&quot; width=&quot;400&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The virtuous cycle that sustained open source for 30 years&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Now the loop runs in reverse. A maintainer shares a library. AI agents use it, but users never visit the docs, never file issues, never sponsor the project. Revenue drops. The maintainer burns out and stops maintaining. Developers who need that functionality ask an AI to build it from scratch. That generated code never gets shared back — why would it? And the next maintainer looking at the economics thinks: why bother sharing mine?&lt;/p&gt;
&lt;p&gt;&amp;lt;img src=&quot;/images/blog/open-source-used-to-death/death-spiral.svg&quot; alt=&quot;The open source death spiral&quot; width=&quot;300&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The same loop — until it isn&apos;t&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Each turn of the cycle is rational. No one&apos;s doing anything wrong. But the collective result is an ecosystem consuming itself.&lt;/p&gt;
&lt;p&gt;The data is already there. &lt;a href=&quot;https://stackoverflow.com/&quot;&gt;Stack Overflow&lt;/a&gt; lost &lt;a href=&quot;https://papers.ssrn.com/sol3/papers.cfm?abstract_id=4990637&quot;&gt;25% of its activity&lt;/a&gt; within six months of &lt;a href=&quot;https://chatgpt.com/&quot;&gt;ChatGPT&lt;/a&gt; launching — and yes, SO was already declining, but AI cratered the curve. The &lt;a href=&quot;https://daniel.haxx.se/&quot;&gt;curl maintainer&lt;/a&gt; reports that &lt;a href=&quot;https://daniel.haxx.se/blog/2024/01/02/the-i-in-llm-stands-for-intelligence/&quot;&gt;20% of security vulnerability reports are now AI-generated garbage&lt;/a&gt;. Downloads go up. Everything that matters goes down.&lt;/p&gt;
&lt;h2&gt;The economics of extraction&lt;/h2&gt;
&lt;p&gt;When cloud providers started offering open source as a service (the &quot;AWS problem&quot;), maintainers at least knew who was extracting value. You could negotiate. You could change your license. You could build a competing hosted product. You could fight it.&lt;/p&gt;
&lt;p&gt;AI extraction is painless — and that&apos;s what makes it lethal. Nobody feels like they&apos;re taking anything. A developer asks Claude a question, gets working code, ships it. The value flows out of open source into training data, into autocomplete suggestions, into vibe-coded projects — and nobody involved ever knows your name. It&apos;s not theft. It&apos;s evaporation.&lt;/p&gt;
&lt;p&gt;The paper puts numbers to it: to sustain open source at current levels, you&apos;d need each user to pay roughly what they pay now. But the whole point of AI-mediated usage is that per-user engagement drops to near zero. The math doesn&apos;t work.&lt;/p&gt;
&lt;h2&gt;What the economists miss&lt;/h2&gt;
&lt;p&gt;The paper leaves out the part where developers do things because they want to, not because they get paid. It acknowledges this blind spot.&lt;/p&gt;
&lt;p&gt;I&apos;ve spent over 20 years in open source — &lt;a href=&quot;https://www.debian.org&quot;&gt;Debian&lt;/a&gt;, &lt;a href=&quot;https://awesomewm.org&quot;&gt;awesome window manager&lt;/a&gt;, &lt;a href=&quot;https://www.gnu.org/software/emacs/&quot;&gt;GNU Emacs&lt;/a&gt;, &lt;a href=&quot;https://www.openstack.org&quot;&gt;OpenStack&lt;/a&gt;, &lt;a href=&quot;https://mergify.com&quot;&gt;Mergify&lt;/a&gt; — and the economics were never the whole story. A lot of open source ran on ego. And I mean that as a compliment.&lt;/p&gt;
&lt;p&gt;You started a project because you were proud of what you built. You maintained it because people used it and told you it was good. You contributed to someone else&apos;s project because it felt meaningful to be part of something bigger. The reputation, the GitHub profile, the conference talks — that was the fuel.&lt;/p&gt;
&lt;p&gt;AI erodes that too. When your library is consumed by a model that never credits you, the ego fuel dries up. Nobody&apos;s filing issues saying &quot;great work on this API.&quot; Nobody&apos;s writing blog posts about your clever design decisions. Your code is in millions of projects and you&apos;ll never know.&lt;/p&gt;
&lt;p&gt;Michael Still &lt;a href=&quot;https://www.madebymikal.com/ancient-code-mental-health-and-ai-tooling/&quot;&gt;maintained pngtools for 25 years&lt;/a&gt; and recently admitted he &quot;can&apos;t really explain what I got in return apart from the occasional dopamine hit.&quot; That&apos;s not bitterness — it&apos;s an honest accounting of what happens when the feedback loop never closes.&lt;/p&gt;
&lt;h2&gt;The rebuild reflex&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.anthropic.com&quot;&gt;Anthropic&lt;/a&gt; &lt;a href=&quot;https://www.anthropic.com/engineering/building-c-compiler&quot;&gt;built a C compiler&lt;/a&gt; with Claude. &lt;a href=&quot;https://openai.com&quot;&gt;OpenAI&lt;/a&gt; &lt;a href=&quot;https://fortune.com/2026/01/23/cursor-built-web-browser-with-swarm-ai-agents-powered-openai/&quot;&gt;built a web browser&lt;/a&gt;. This is what happens when development costs collapse.&lt;/p&gt;
&lt;p&gt;The obvious objection: generating code isn&apos;t maintaining code. curl works because of 20 years of edge cases, security patches, and platform quirks. You can&apos;t generate that in a weekend. True — but the line between &quot;writing&quot; code and &quot;maintaining&quot; code is blurrier than it looks. Every line you write immediately becomes maintenance. AI doesn&apos;t just generate the first draft — it fixes the bugs, handles the edge cases, iterates on the patches. The entire lifecycle gets cheaper, not just the initial build.&lt;/p&gt;
&lt;p&gt;Five years ago, nobody in their right mind would build their own HTTP server, their own date parsing library, their own compression algorithm. You used the shared one because the alternative was insane.&lt;/p&gt;
&lt;p&gt;The alternative is no longer insane. It might be a weekend project.&lt;/p&gt;
&lt;h2&gt;Where this leaves us&lt;/h2&gt;
&lt;p&gt;Some of this is happening right now. The Tailwind numbers are a Q4 report. Stack Overflow&apos;s decline is measured. The &lt;a href=&quot;https://curl.se/&quot;&gt;curl&lt;/a&gt; maintainer is drowning in AI-generated noise today. Some of it is projection — I&apos;m betting that the diversion effect gets stronger, not weaker, as AI gets better. I could be wrong. But the trend lines all point the same way.&lt;/p&gt;
&lt;p&gt;&quot;But AI also contributes!&quot; Sure. Agents file PRs, generate docs, triage issues. That helps with the mechanical work. It doesn&apos;t replace the human who cared enough to read your code and tell you it mattered. The engagement that sustained open source was never about the pull requests — it was about the people behind them.&lt;/p&gt;
&lt;p&gt;Open source isn&apos;t dying because people stopped caring. It&apos;s dying because AI lets people extract all the value without returning any of it. The code flows through models, through agents, through autocomplete — and none of it flows back.&lt;/p&gt;
&lt;p&gt;The question isn&apos;t whether this is happening. It&apos;s what comes next.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://julien.danjou.info/blog/open-source-after-the-extraction&quot;&gt;Part 2: Open Source After the Extraction&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>How Entire Works Under the Hood</title><link>https://julien.danjou.info/blog/how-entire-works-under-the-hood/</link><guid isPermaLink="true">https://julien.danjou.info/blog/how-entire-works-under-the-hood/</guid><description>I dug into Entire&apos;s open source Checkpoints CLI. It&apos;s a clever abuse of git internals — shadow branches, orphan metadata, and a session state machine. Here&apos;s how it works.</description><pubDate>Thu, 12 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In &lt;a href=&quot;https://julien.danjou.info/blog/github-wont-work-for-ai-agents&quot;&gt;part 1&lt;/a&gt;, I covered why Entire raised $60M and what problem they&apos;re solving. Now let&apos;s look at the actual code.&lt;/p&gt;
&lt;p&gt;I pointed Claude Code at &lt;a href=&quot;https://github.com/entireio/cli&quot;&gt;Entire&apos;s open source CLI&lt;/a&gt; and asked it to explain how things work. The architecture is more interesting than I expected — they&apos;ve essentially built a session-aware metadata layer on top of git using nothing but git&apos;s own primitives.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/entire/repo.png&quot; alt=&quot;The Entire CLI repository on GitHub&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;The Big Picture&lt;/h2&gt;
&lt;p&gt;Entire hooks into two things: your AI agent (Claude Code, Gemini CLI) and git itself. The agent hooks capture what&apos;s happening during a session. The git hooks capture what the developer commits.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Agent hooks (Claude Code)         Git hooks
  SessionStart                     prepare-commit-msg
  UserPromptSubmit                 post-commit
  Stop                             pre-push
  PreToolUse / PostToolUse
         │                              │
         └──────────┬───────────────────┘
                    │
            ┌───────▼────────┐
            │   Strategy     │
            │                │
            │ SaveChanges()  │
            │ Rewind()       │
            │ Condense()     │
            └───────┬────────┘
                    │
         ┌──────────┴──────────┐
         │                     │
    Shadow branches      Metadata branch
    (local, temp)        (shared, permanent)
    entire/&amp;lt;hash&amp;gt;        entire/checkpoints/v1
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;How Agent Hooks Get Installed&lt;/h2&gt;
&lt;p&gt;Running &lt;code&gt;entire enable&lt;/code&gt; writes hook entries into &lt;code&gt;.claude/settings.json&lt;/code&gt;. Seven hooks, covering the full session lifecycle:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SessionStart/SessionEnd&lt;/strong&gt; — track session boundaries&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UserPromptSubmit&lt;/strong&gt; — fires before the agent starts working (captures human edits)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stop&lt;/strong&gt; — fires after the agent finishes a turn (triggers checkpoint save)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PreToolUse/PostToolUse[Task]&lt;/strong&gt; — track subagent spawning&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PostToolUse[TodoWrite]&lt;/strong&gt; — capture task state&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each hook is just a shell command: &lt;code&gt;entire hooks claude-code stop&lt;/code&gt;. The CLI parses the agent&apos;s transcript to extract everything it needs.&lt;/p&gt;
&lt;h2&gt;The Transcript Is the Source of Truth&lt;/h2&gt;
&lt;p&gt;This is the key insight. When the Stop hook fires, Claude Code passes two things via stdin: a &lt;code&gt;session_id&lt;/code&gt; and a &lt;code&gt;transcript_path&lt;/code&gt;. That transcript — the JSONL file where Claude logs every message, tool call, and response — is the single source of truth.&lt;/p&gt;
&lt;p&gt;The CLI mines it for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Modified files&lt;/strong&gt; — scans for &lt;code&gt;tool_use&lt;/code&gt; blocks where the tool is &lt;code&gt;Write&lt;/code&gt;, &lt;code&gt;Edit&lt;/code&gt;, etc., and extracts the &lt;code&gt;file_path&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;User prompts&lt;/strong&gt; — finds &lt;code&gt;type: &quot;user&quot;&lt;/code&gt; entries&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Token usage&lt;/strong&gt; — sums &lt;code&gt;input_tokens&lt;/code&gt;, &lt;code&gt;output_tokens&lt;/code&gt; from response metadata&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Summary&lt;/strong&gt; — grabs the last assistant message&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No magic, no APIs. It just reads the same JSONL file that Claude Code writes to disk.&lt;/p&gt;
&lt;h2&gt;Shadow Branches: Snapshots Without Commits&lt;/h2&gt;
&lt;p&gt;Here&apos;s where it gets clever. When the agent finishes a turn, Entire needs to save a snapshot of the working tree. But it can&apos;t commit to your branch — that would mess up your history.&lt;/p&gt;
&lt;p&gt;So it creates &lt;strong&gt;shadow branches&lt;/strong&gt;: refs like &lt;code&gt;entire/2b4c177-a5e3f2&lt;/code&gt; that live in your local repo but never touch your working branch.&lt;/p&gt;
&lt;p&gt;The name encodes two things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;2b4c177&lt;/code&gt; — first 7 chars of HEAD when the session started&lt;/li&gt;
&lt;li&gt;&lt;code&gt;a5e3f2&lt;/code&gt; — hash of the worktree ID (to support &lt;code&gt;git worktree&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The snapshot is built entirely in memory using go-git&apos;s plumbing APIs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Take HEAD&apos;s tree (the full repo structure)&lt;/li&gt;
&lt;li&gt;Apply the agent&apos;s changes (add/remove/modify blobs)&lt;/li&gt;
&lt;li&gt;Attach the metadata directory (&lt;code&gt;.entire/metadata/&amp;lt;session-id&amp;gt;/&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Create a commit on the shadow branch&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;No checkout, no stash, no visible side effects. The user and agent don&apos;t even know it happened.&lt;/p&gt;
&lt;p&gt;Deduplication is automatic: if the tree hash matches the previous checkpoint, it skips the commit. Git&apos;s content-addressable storage means identical files share blobs across checkpoints.&lt;/p&gt;
&lt;h2&gt;The Condensation Model&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/entire/branch.png&quot; alt=&quot;The entire/checkpoints/v1 orphan branch stores all metadata&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Shadow branches are local scratch space. The real metadata lives on &lt;code&gt;entire/checkpoints/v1&lt;/code&gt; — an orphan branch (no common ancestor with your code) that&apos;s pushed alongside your regular branches.&lt;/p&gt;
&lt;p&gt;The flow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Agent works → checkpoints saved on shadow branch (local)&lt;/li&gt;
&lt;li&gt;You commit → &lt;code&gt;post-commit&lt;/code&gt; hook fires&lt;/li&gt;
&lt;li&gt;&lt;code&gt;prepare-commit-msg&lt;/code&gt; adds a trailer: &lt;code&gt;Entire-Checkpoint: a3b2c4d5e6f7&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Shadow branch data gets &lt;strong&gt;condensed&lt;/strong&gt; — copied into the metadata branch&lt;/li&gt;
&lt;li&gt;Shadow branch gets cleaned up&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The checkpoint ID (&lt;code&gt;a3b2c4d5e6f7&lt;/code&gt;) is 6 random bytes, not a git SHA. It&apos;s sharded into a directory path on the metadata branch:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;entire/checkpoints/v1  (orphan branch)
└── a3/b2c4d5e6f7/
    ├── metadata.json          # summary, attribution, token usage
    ├── 0/
    │   ├── full.jsonl         # complete session transcript
    │   ├── prompt.txt         # user prompts
    │   └── context.md         # generated context
    └── 1/                     # additional sessions if any
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That one-line trailer in your commit — &lt;code&gt;Entire-Checkpoint: a3b2c4d5e6f7&lt;/code&gt; — is the bidirectional link. From the commit you find metadata via the sharded path. From the metadata you find the commit by searching for the trailer.&lt;/p&gt;
&lt;h2&gt;Attribution: Who Wrote What?&lt;/h2&gt;
&lt;p&gt;This is the piece that matters for engineering leads. Entire tracks line-level code attribution: what percentage was agent-written vs. human-written.&lt;/p&gt;
&lt;p&gt;The trick is the &lt;strong&gt;UserPromptSubmit&lt;/strong&gt; hook. Every time you type a new prompt — &lt;em&gt;before&lt;/em&gt; the agent starts working — the CLI snapshots the worktree diff against the last checkpoint. This captures exactly what you changed between agent turns.&lt;/p&gt;
&lt;p&gt;By commit time, it has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Agent lines&lt;/strong&gt;: changes from the last checkpoint&apos;s tree&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Human added&lt;/strong&gt;: lines you added between prompts&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Human modified&lt;/strong&gt;: lines you edited in agent-written code&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agent percentage&lt;/strong&gt;: the ratio&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The result is stored in &lt;code&gt;initial_attribution&lt;/code&gt; in the metadata:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;agent_lines&quot;: 150,
  &quot;human_added&quot;: 25,
  &quot;human_modified&quot;: 10,
  &quot;agent_percentage&quot;: 85.7
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It even uses a LIFO heuristic for self-modifications — if you add lines then remove lines from the same file, it assumes you&apos;re removing your own first, not penalizing the agent&apos;s contribution.&lt;/p&gt;
&lt;h2&gt;Multi-Developer: Conflict-Free by Design&lt;/h2&gt;
&lt;p&gt;The metadata branch gets pushed during &lt;code&gt;git push&lt;/code&gt; (via the &lt;code&gt;pre-push&lt;/code&gt; hook). Multiple developers push to the same &lt;code&gt;entire/checkpoints/v1&lt;/code&gt; branch.&lt;/p&gt;
&lt;p&gt;This works because checkpoint IDs are random — two developers will essentially never produce the same 12-hex-char ID. Merging is just a tree union: flatten both trees, combine entries, done. No merge conflicts possible.&lt;/p&gt;
&lt;p&gt;If a normal push fails (non-fast-forward), the CLI fetches the remote, merges trees, creates a merge commit, and retries.&lt;/p&gt;
&lt;h2&gt;What&apos;s Missing&lt;/h2&gt;
&lt;p&gt;The architecture is solid engineering, but a few things stood out:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Transcript privacy.&lt;/strong&gt; Session transcripts (full agent conversations) get pushed to a branch anyone with repo access can read. For private repos, maybe fine. For orgs with varying access levels — that&apos;s a problem.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Squash merges break links.&lt;/strong&gt; If a PR with 5 commits (each with &lt;code&gt;Entire-Checkpoint&lt;/code&gt; trailers) gets squash-merged, those trailers disappear. The metadata exists but the bidirectional link from the merged commit is broken.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The metadata branch grows forever.&lt;/strong&gt; Every session from every developer, including abandoned PRs and throwaway experiments, accumulates on &lt;code&gt;entire/checkpoints/v1&lt;/code&gt;. There&apos;s an &lt;code&gt;entire clean&lt;/code&gt; command for local shadow branches, but no retention policy for the permanent metadata. For a large team over months, that&apos;ll bloat.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No PR linkage.&lt;/strong&gt; The branch name is stored, but there&apos;s no PR number or URL. You can&apos;t easily ask &quot;show me all sessions related to PR #42.&quot;&lt;/p&gt;
&lt;h2&gt;The Smart Parts&lt;/h2&gt;
&lt;p&gt;What I genuinely admire:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Git as a free database.&lt;/strong&gt; Shadow branches store full repo snapshots, but git&apos;s content-addressable storage means only changed blobs cost anything. You get atomic snapshots, deduplication, and transport for free.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In-memory tree building.&lt;/strong&gt; Checkpoints are created through go-git plumbing APIs — no worktree checkout, no stash, nothing visible. Zero disruption to the developer&apos;s flow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Attribution at prompt boundaries.&lt;/strong&gt; Capturing human edits &lt;em&gt;before&lt;/em&gt; the agent contaminates the worktree is the cleanest measurement point possible.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Shadow branch migration.&lt;/strong&gt; If you rebase or pull (HEAD changes), the shadow branch name automatically updates. Your session continues seamlessly. This handles a common workflow that would otherwise silently break.&lt;/p&gt;
&lt;h2&gt;So What?&lt;/h2&gt;
&lt;p&gt;Entire doesn&apos;t solve a burning problem today. Most of us are fine with agent-written code landing in our repos without detailed provenance. But the trajectory is clear: as agents write more code, the audit trail becomes essential.&lt;/p&gt;
&lt;p&gt;The approach of storing session context alongside code in git — rather than in a separate system — is the right architectural bet. Git is already where your code lives, where your CI runs, where your reviews happen. Adding a metadata layer inside git itself (instead of a SaaS dashboard somewhere) means the context travels with the code.&lt;/p&gt;
&lt;p&gt;Whether Entire is the company that turns this into a platform worth $300M is above my pay grade. But the engineering is genuine, the problem is real, and the timing feels right.&lt;/p&gt;
&lt;p&gt;I&apos;ll be watching.&lt;/p&gt;
</content:encoded></item><item><title>Agent-Written Code Needs More Than Git</title><link>https://julien.danjou.info/blog/github-wont-work-for-ai-agents/</link><guid isPermaLink="true">https://julien.danjou.info/blog/github-wont-work-for-ai-agents/</guid><description>The former GitHub CEO just raised $60M to rebuild developer tooling for the agentic era. He might be right that git needs a rethink — I&apos;ve been hacking around the same problems.</description><pubDate>Wed, 11 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The former GitHub CEO just raised $60M at a $300M valuation for a seed round. For a CLI tool. Let that sink in.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/entire/entire-io.png&quot; alt=&quot;Entire.io — a new developer platform for the agentic era&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Thomas Dohmke left GitHub and launched &lt;a href=&quot;https://entire.io/blog/hello-entire-world&quot;&gt;Entire&lt;/a&gt;, a developer platform built from scratch for the age of AI coding agents. It&apos;s the largest seed round in dev tools history.&lt;/p&gt;
&lt;p&gt;My first reaction was &quot;that&apos;s insane.&quot; My second reaction was &quot;wait, I&apos;ve been solving the same problem with duct tape and hooks.&quot;&lt;/p&gt;
&lt;h2&gt;The Problem Is Real&lt;/h2&gt;
&lt;p&gt;If you&apos;re using AI agents like Claude Code or Gemini CLI daily — and I am — you&apos;ve already felt it. Git was built for humans writing code. It assumes you know what you changed and why. It assumes your commit messages mean something. It assumes the person who wrote the code will remember what they were thinking.&lt;/p&gt;
&lt;p&gt;AI agents break all of that.&lt;/p&gt;
&lt;p&gt;When Claude Code rewrites a module for me, the commit message says what happened, but not &lt;em&gt;why&lt;/em&gt;. There&apos;s no trace of the conversation that led there. No record of the three approaches the agent considered and rejected. No way to know if the prompt was &quot;refactor this for clarity&quot; or &quot;make this 10x faster and I don&apos;t care about readability.&quot;&lt;/p&gt;
&lt;p&gt;The transcript — the actual reasoning behind the code — lives in a terminal session that vanishes when you close the tab.&lt;/p&gt;
&lt;h2&gt;My Duct Tape Solution&lt;/h2&gt;
&lt;p&gt;I ran into this a few weeks ago when I wanted to resume a Claude Code session after a reboot. The session was gone, and I had no idea what context the agent had when it made certain decisions.&lt;/p&gt;
&lt;p&gt;So I did what any engineer would do: I wrote a hook. A simple Claude Code hook that links each commit to its session ID via a git trailer. Nothing fancy — just enough that I can trace a commit back to the conversation that produced it.&lt;/p&gt;
&lt;p&gt;Combined with &lt;a href=&quot;https://github.com/Mergify/mergify-cli&quot;&gt;Mergify&apos;s CLI&lt;/a&gt; for stacking PRs, it made my workflow usable. But it&apos;s duct tape. It doesn&apos;t capture the transcript, doesn&apos;t track attribution, doesn&apos;t handle multi-session work.&lt;/p&gt;
&lt;p&gt;Which is exactly the gap Entire is going after.&lt;/p&gt;
&lt;h2&gt;What Entire Actually Claims to Be&lt;/h2&gt;
&lt;p&gt;Beyond the buzzwords in the press release, Entire is shipping three things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Checkpoints&lt;/strong&gt; — an open source CLI that captures session context (prompts, transcripts, reasoning) alongside every commit, stored in git without polluting your history&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A semantic reasoning layer&lt;/strong&gt; — meant to let multiple AI agents collaborate on the same codebase with shared context&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An AI-native UI&lt;/strong&gt; — designed for agent-to-human collaboration rather than human-to-human&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;They&apos;re not claiming to have a finished product — and they&apos;re upfront about it. The Checkpoints CLI is the first concrete thing they&apos;ve shipped, and it&apos;s open source. The rest is where the $60M goes. Fair enough — let&apos;s look at what actually exists.&lt;/p&gt;
&lt;h2&gt;Why $60M for This?&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/entire/techcrunch.png&quot; alt=&quot;TechCrunch: Former GitHub CEO raises record $60M dev tool seed round&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The bet isn&apos;t that the current CLI is worth $300M. The bet is that the developer tooling stack needs to be rebuilt for a world where most code is written by agents, and the first company to nail the foundation wins.&lt;/p&gt;
&lt;p&gt;Think about it: if 99% of code is agent-written in two years (which is where things are heading), then the code review, debugging, and understanding workflow we have today is fundamentally broken. You can&apos;t review AI-written code the same way you review human-written code. You need the &lt;em&gt;context&lt;/em&gt; — what was the agent trying to do, what constraints did it have, what alternatives did it consider.&lt;/p&gt;
&lt;p&gt;That&apos;s a platform opportunity, and $60M is the price of a credible attempt at it. Whether Entire is the one to build it is a different question — but the problem is real and urgent.&lt;/p&gt;
&lt;h2&gt;My Take&lt;/h2&gt;
&lt;p&gt;Dohmke knows exactly where GitHub&apos;s limits are (he ran it). The investor list — Felicis, Madrona, Olivier Pomel — signals real conviction. And the core insight, that agent context is as important as the code itself, is something I believe in my bones because I&apos;ve been hacking around it myself.&lt;/p&gt;
&lt;p&gt;Their long-term ambition seems to involve moving beyond git. I&apos;m more dubious about that part. Git is unkillable. My bet is that the reality will be hooks and duct tape around git for the next few years — and honestly, that&apos;s probably enough. Git&apos;s data model bends a lot further than people think before it breaks.&lt;/p&gt;
&lt;p&gt;There&apos;s a deeper tension, though. Entire&apos;s model assumes humans are still in the loop — driving agents, reviewing output, caring about attribution. But that&apos;s already not quite how it works. I haven&apos;t written a line of code in months. I describe what I want, the agent writes it, I tell it to fix its mistakes, and it does. I&apos;m not a developer anymore — I&apos;m a director.&lt;/p&gt;
&lt;p&gt;And the trajectory is obvious: agents won&apos;t need directors much longer either. If agents are fully autonomous, who&apos;s the audience for commit context and session transcripts? The agent doesn&apos;t need to remember what it was thinking — it can just re-derive it. The human who never touched the code doesn&apos;t need line-level attribution.&lt;/p&gt;
&lt;p&gt;That could go either way for Entire. Maybe full autonomy makes provenance &lt;em&gt;more&lt;/em&gt; critical — precisely because no human was involved, you need a machine-readable audit trail. Or maybe it makes the whole problem vanish — agents that manage their own context don&apos;t need git hacks to preserve it.&lt;/p&gt;
&lt;p&gt;Either way, if you&apos;re leading an engineering team right now, you should be thinking about how you&apos;ll audit, understand, and trust the code your agents produce — whether there&apos;s a human in the loop or not.&lt;/p&gt;
&lt;p&gt;Next up, I&apos;ll dig into the actual source code and show you &lt;a href=&quot;https://julien.danjou.info/blog/how-entire-works-under-the-hood&quot;&gt;how Entire&apos;s Checkpoints CLI works under the hood&lt;/a&gt;. It&apos;s a clever piece of engineering that abuses git internals in ways I genuinely admire.&lt;/p&gt;
</content:encoded></item><item><title>So I Will Never Write Code Again</title><link>https://julien.danjou.info/blog/so-i-will-never-write-code-again/</link><guid isPermaLink="true">https://julien.danjou.info/blog/so-i-will-never-write-code-again/</guid><description>I&apos;ve been coding for 25 years. Since January, I haven&apos;t written a single line. And it feels like relief.</description><pubDate>Tue, 10 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/nocode.png&quot; alt=&quot;Illustration of a developer who has stopped writing code by hand&quot; /&gt;&lt;/p&gt;
&lt;p&gt;A year ago, I thought AI-assisted coding was going to be a nice productivity boost. Generate a Python script with ChatGPT, copy-paste it somewhere, save twenty minutes. I figured that was the next five years: small wins, gradual improvement.&lt;/p&gt;
&lt;p&gt;Then last August, I &lt;a href=&quot;https://julien.danjou.info/blog/vibe-coding-a-feature-with-ai/&quot;&gt;wrote a feature where Copilot did about 80% of the work&lt;/a&gt;. I thought: okay, it&apos;s getting closer.&lt;/p&gt;
&lt;p&gt;Since January, I haven&apos;t written a single line of code.&lt;/p&gt;
&lt;p&gt;I want to be precise: I&apos;ve &lt;em&gt;produced&lt;/em&gt; a lot of code. More than ever, probably. But I didn&apos;t write any of it. I steer. I review. I architect. I don&apos;t type.&lt;/p&gt;
&lt;p&gt;And I don&apos;t feel the urge to go back.&lt;/p&gt;
&lt;p&gt;This might sound like grief. I&apos;ve been coding for 25 years. I wrote C for a window manager, Lisp for Emacs, Python for everything else. For most of my career, coding was a thing that defined me. Losing that should feel like losing a part of myself.&lt;/p&gt;
&lt;p&gt;But it doesn&apos;t. It feels like relief.&lt;/p&gt;
&lt;p&gt;For years, I was frustrated. I had more ideas than I could build. The bottleneck was never thinking, it was typing. Translating architecture into syntax, aligning parentheses, naming variables, fighting linters. The fun was in the &lt;em&gt;solving&lt;/em&gt;, not the &lt;em&gt;writing&lt;/em&gt;. And now the writing part is handled.&lt;/p&gt;
&lt;p&gt;I still enjoy reading code. It&apos;s like reading a good book. Understanding how pytest works internally, tracing through a complex system, that remains satisfying. But when the goal is to produce, AI beats everything.&lt;/p&gt;
&lt;p&gt;This is actually the second time I&apos;ve stepped away from code. The first was when I became CEO. That time, it was forced. I didn&apos;t choose to stop. I just ran out of hours. There was always one more meeting, one more hire, one more decision that pushed coding to the evening, then to the weekend, then to never.&lt;/p&gt;
&lt;p&gt;That &lt;em&gt;was&lt;/em&gt; grief. A slow, reluctant surrender.&lt;/p&gt;
&lt;p&gt;This time is different. I&apos;m not being pushed away. I&apos;m choosing to work at a higher layer. The same way I once chose Python over C, because life is short and the abstraction was worth it. AI is just the next rung.&lt;/p&gt;
&lt;p&gt;The creativity doesn&apos;t stop. If anything, it accelerates. You still design systems, still make architectural choices, still think about data models and trade-offs. You just don&apos;t spend hours translating those decisions into semicolons. The craft moves up a level, and that&apos;s fine.&lt;/p&gt;
&lt;p&gt;I know this will be harder for others. My colleague Rémy &lt;a href=&quot;https://mergify.com/blog/claude-didnt-kill-craftsmanship&quot;&gt;wrote about whether AI is killing craftsmanship&lt;/a&gt;. For engineers who defined themselves by the elegance of their code, by the perfectly named function, by the satisfaction of a clean diff, this shift feels like losing something sacred.&lt;/p&gt;
&lt;p&gt;I get it. Writing C was a beautiful puzzle. Lisp was genuinely fun. And I still think learning to code by hand matters, the same way learning assembly helps you understand memory even if you never write it professionally.&lt;/p&gt;
&lt;p&gt;But I&apos;m not going to fight a paradigm shift out of nostalgia. The ride was great. The next one looks better.&lt;/p&gt;
&lt;p&gt;I think the flow state people mourn isn&apos;t gone. It&apos;s just moving. Steering AI toward clean architecture, making the right system-level decisions, reviewing output with deep context, that has its own rhythm. The interruptions are still too frequent today (too many permission prompts), but the direction is clear. The flow will come back. It&apos;ll just be at a different altitude.&lt;/p&gt;
&lt;p&gt;If you&apos;re a senior engineer feeling this shift approaching, here&apos;s what I&apos;d say: the grief you&apos;re expecting might not be grief at all. The bottleneck was never the thinking. It was the typing. And the thinking is still yours.&lt;/p&gt;
</content:encoded></item><item><title>The Pre-AI Timestamp</title><link>https://julien.danjou.info/blog/the-pre-ai-timestamp/</link><guid isPermaLink="true">https://julien.danjou.info/blog/the-pre-ai-timestamp/</guid><description>In a few years, the only proof something is real will be that it existed before AI did.</description><pubDate>Thu, 29 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I was watching the news this week. &lt;a href=&quot;https://www.yahoo.com/news/articles/experts-issue-warning-viral-videos-033000851.html&quot;&gt;A segment about AI-generated fake videos of snowstorms in the US and Russia&lt;/a&gt;. Journalists carefully debunking synthetic footage, frame by frame.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/f1be059b-808a-4e00-a8b7-aebc122fc7f5_462x704.png&quot; alt=&quot;AI-generated image of a fake snowstorm video&quot; /&gt;
&lt;em&gt;AI generated image&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I thought: this won’t scale.&lt;/p&gt;
&lt;p&gt;Right now, we’re in a strange transitional moment. People can still tell the difference between AI-generated content and reality. We spot the weird hands, the uncanny smoothness, the details that don’t quite land. News organizations debunk fakes. We feel like we’re staying ahead of it.&lt;/p&gt;
&lt;p&gt;But this is temporary. In a year or two, AI-generated video will be indistinguishable from real footage. And then what?&lt;/p&gt;
&lt;p&gt;Here’s what I think people aren’t grasping: the challenge isn’t “how do we detect AI content?” That’s the 2026 problem. The real challenge is what comes after, when detection becomes impossible.&lt;/p&gt;
&lt;p&gt;The only way I know Coldplay is a real band is that they existed before AI did. I have pre-AI memory. I remember when they started. I’ve seen them referenced in media that predates synthetic content. That history is my anchor.&lt;/p&gt;
&lt;p&gt;Now imagine a new band starting in 2030. How would I know they’re real? Unless I go to their concert and see them on stage, I can’t. Their music could be generated. Their interviews could be synthetic. Their social media presence could be entirely fabricated. There’s no way to verify.&lt;/p&gt;
&lt;p&gt;And when I say I go, I mean me. I can’t trust anyone online that I don’t know personally. There won’t be a way for you to know if you’re talking to a real human via a computerized interface in a few years. Your online friends could be AI for what you know.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/aa9fe534-04a2-469b-8cde-5df0d5a07012_1456x816.png&quot; alt=&quot;Illustration of the erosion of online trust as AI content becomes indistinguishable&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This applies to everything. An influencer recommending restaurants. A journalist breaking news. A newsletter you just discovered. If it started after AI became indistinguishable, you have no anchor. You can’t know.&lt;/p&gt;
&lt;p&gt;And this is something most people can’t grasp today because they lived in the pre-AI era, and they have this anchor. Future generations won’t have it.&lt;/p&gt;
&lt;p&gt;We’ve already lived through a version of this with fake reviews. For the last decade, we’ve learned to distrust Amazon ratings, Yelp scores, app store reviews. We developed heuristics. We looked for patterns.&lt;/p&gt;
&lt;p&gt;But that was humans writing fake reviews at a human scale. Now imagine AI generating reviews at a scale we can’t comprehend. Every product, every restaurant, every service flooded with synthetic opinions indistinguishable from real ones. The heuristics break. Trust collapses.&lt;/p&gt;
&lt;p&gt;The same thing will happen to media, to news, to social networks, to everything online. &lt;a href=&quot;https://julien.danjou.info/blog/ai-feels-like-1999-all-over-again&quot;&gt;AI feels like 1999 all over again&lt;/a&gt; — except this time, the divide isn&apos;t access. It&apos;s whether you can tell what&apos;s real.&lt;/p&gt;
&lt;p&gt;A lot of trust today is based on consensus. We trust something because many people trust it. But when bots can outnumber people, consensus becomes meaningless. Popularity becomes a metric that anyone can manufacture.&lt;/p&gt;
&lt;p&gt;So what’s left?&lt;/p&gt;
&lt;p&gt;Physical presence. Meeting someone in person. Attending a concert. Being there.&lt;/p&gt;
&lt;p&gt;Real life becomes the last trust anchor. The thing that can’t be faked (at least until humanoid robots become indistinguishable too, but that’s a problem for later).&lt;/p&gt;
&lt;p&gt;Here’s what haunts me: in two generations, no one alive will remember what was pre-AI. The generational memory dies. A teenager in 2050 won’t know that The New York Times existed before AI and is therefore trustable because run by humans (I can imagine it’d still be the case). They won’t have the anchor I have. Everything in their world will be post-AI, and nothing online will be verifiable.&lt;/p&gt;
&lt;p&gt;They’ll have to assume everything is fake. That’s the default. And building trust from that baseline is something we’ve never had to do before.&lt;/p&gt;
&lt;p&gt;I don’t have a solution. But I think we’re in a narrow window where we still remember what “real” meant. That memory is more valuable than we realize.&lt;/p&gt;
</content:encoded></item><item><title>AI Won’t Kill Juniors. It Will Expose Seniors.</title><link>https://julien.danjou.info/blog/ai-wont-kill-juniors-it-will-expose/</link><guid isPermaLink="true">https://julien.danjou.info/blog/ai-wont-kill-juniors-it-will-expose/</guid><description>Everyone fears for the juniors. But the engineers who stopped growing at the wrong layer have more to lose.</description><pubDate>Wed, 21 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The tech industry has a new consensus: AI will kill junior engineering jobs. Look at any discussion thread, and you’ll find the same narrative. Juniors are doomed. They’ll never learn to code properly. The entry-level pipeline is broken.&lt;/p&gt;
&lt;p&gt;I’m not so sure. When I look at junior engineers today, I see people who are used to learning. They came up through boot camps, YouTube tutorials,and constantly shifting frameworks. Adapting is what they do. They might struggle for a year or two, but they’ll figure it out.&lt;/p&gt;
&lt;p&gt;The engineers I’m worried about are the senior ones.&lt;/p&gt;
&lt;p&gt;Sure, not all of them. But the ones who plateaued at “code craftsman” and never moved up.&lt;/p&gt;
&lt;p&gt;I’ve seen it play out already. A standup where someone proudly reports they spent the day fixing a batch of bugs and shipping a couple of pull requests. The rest of the team glances at each other. They’re thinking: *that’s ten minutes of Claude Code. Why did you spend eight hours in your IDE?*&lt;/p&gt;
&lt;p&gt;This isn’t new. We’ve seen it before. When bash gave way to Perl. When Java replaced C for most applications. Every paradigm shift leaves some people behind. Maybe 10%, maybe 20%, clinging to the old way because it’s what they know.&lt;/p&gt;
&lt;p&gt;But AI is different. The shift is faster. The impact is more massive. And the reach is exponential.&lt;/p&gt;
&lt;p&gt;Here’s the pattern I see. When I started programming, you’d learn assembly. Then you’d switch to C because life is short. Then Python, because life is really short. Each jump felt like cheating to the previous generation, and each one freed you to think at a higher level.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/b855f1c4-142b-482c-8cee-8d02e878cd3a_1456x816.webp&quot; alt=&quot;Illustration of programming abstraction levels from assembly to AI&quot; /&gt;&lt;/p&gt;
&lt;p&gt;AI is the next rung on that ladder. I hope schools are teaching this now: learn to write code by hand first (you need to understand what you’re abstracting), then switch to AI-assisted development. Just like you learned assembly to understand memory, then moved on. Though knowing how slow institutions adapt, I’m not holding my breath.&lt;/p&gt;
&lt;p&gt;The engineers who get this are thriving. Staff engineers, principal engineers, people whose job was already 70% architecture, cross-team coordination, and system design. They only coded 30% of the time anyway. Now they use AI to multiply that 30% and have even more impact. For them, AI is a force multiplier on an already leveraged role.&lt;/p&gt;
&lt;p&gt;But there’s another group. Senior engineers, five to ten years in, who still think their job is writing code 90% of the time. They never thought deeply about data models. Never cared much about architecture. Never moved toward the work that would make them staff or principal.&lt;/p&gt;
&lt;p&gt;Their entire value was &quot;writing proper, clean code that runs well and passes the linter.&quot; They never invested in the skills that &lt;a href=&quot;https://julien.danjou.info/blog/how-to-be-a-great-software-engineer&quot;&gt;make a great software engineer&lt;/a&gt; — communication, system thinking, judgment.&lt;/p&gt;
&lt;p&gt;That value just evaporated.&lt;/p&gt;
&lt;p&gt;And here’s what makes it worse: working with AI is fundamentally communication work. The engineers who thrive are the ones who already know how to share context, explain problems to colleagues, and filter signal from noise across teams.&lt;/p&gt;
&lt;p&gt;I’ve watched engineers struggle with AI because they won’t invest in communication. They type “fix this bug” without the stack trace, without the constraints, without explaining how production differs from their local setup. They keep the context in their head because explaining feels costly. The result is garbage, and they blame the tool.&lt;/p&gt;
&lt;p&gt;What they don’t see: AI compounds. The more context you feed it about your project, the better it gets. But that requires upfront investment in articulation. If you spent your career avoiding that investment with humans, you’ll prevent it with AI too.&lt;/p&gt;
&lt;p&gt;I don’t have a clean solution. The engineers who won’t adapt will stagnate. They might find work in industries that are slow to change. But it won’t be a great career. It never is when you’re holding onto the last paradigm.&lt;/p&gt;
&lt;p&gt;The engineers at risk aren’t the ones who don’t know enough yet. They’re the ones who stopped growing at the wrong layer. Juniors will climb. The question is whether the seniors stuck in the middle will climb with them.&lt;/p&gt;
</content:encoded></item><item><title>Tech Is the Easy Part</title><link>https://julien.danjou.info/blog/tech-is-the-easy-part/</link><guid isPermaLink="true">https://julien.danjou.info/blog/tech-is-the-easy-part/</guid><description>The hard part isn&apos;t building. It&apos;s everything else.</description><pubDate>Tue, 13 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last week, a founder reached out for advice. Two co-founders — one technical, one not — a couple years of R&amp;amp;D, patents pending. They’d built something they described as a breakthrough: AI-powered app generation, any app, a few hours. They were ready to raise several million euros. Think &lt;a href=&quot;https://lovable.dev&quot;&gt;Lovable&lt;/a&gt;, but better.&lt;/p&gt;
&lt;p&gt;I asked the usual questions. What’s the product? Who’s the customer? How do you distribute it? What’s your unfair advantage?&lt;/p&gt;
&lt;p&gt;The answers were thin. No clear vertical. No distribution plan. No product, really, just capability.&lt;/p&gt;
&lt;p&gt;I pushed on all of this for an hour. And then the non-technical co-founder said the thing that made me sit back in my chair:&lt;/p&gt;
&lt;p&gt;“Well, we did the hard part. We solved the tech.”&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/b00884f6-eda2-4339-a516-44bed1e72309_1376x864.png&quot; alt=&quot;Illustration of tech being the easy part of building a startup&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Here’s what’s actually happening.&lt;/p&gt;
&lt;p&gt;If you’re an engineer — or if you’ve built a company around one — tech &lt;em&gt;feels&lt;/em&gt; hard because it is hard. It’s complex, demanding, and requires real skill. But it’s also legible. You write code, you get feedback. You solve a problem, you know it’s solved. Effort correlates with output. The system makes sense.&lt;/p&gt;
&lt;p&gt;The rest of the business doesn’t work like that.&lt;/p&gt;
&lt;p&gt;Distribution is not legible. You can do everything right and still fail. Positioning is not legible: you’re trying to exist in someone else’s mind, and you can’t compile that. Saying no to opportunities, picking a vertical, pricing, hiring, firing; none of it gives you the clean dopamine hit of a passing test suite.&lt;/p&gt;
&lt;p&gt;So technical teams do what’s rational: they retreat to where effort is rewarded. They keep building. They add features. They refactor. They convince themselves that if the tech is good enough, the rest will follow. (I wrote about this exact pattern in &lt;a href=&quot;https://julien.danjou.info/blog/the-engineers-dilemma-what-we-did&quot;&gt;The Engineer’s Dilemma&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;It won’t.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Ben Horowitz called his book &lt;em&gt;&lt;a href=&quot;https://www.amazon.com/Hard-Thing-About-Things-Building/dp/0062273205&quot;&gt;The Hard Thing About Hard Things&lt;/a&gt;&lt;/em&gt; because the hard things aren’t the technical problems. They’re the ambiguous, human, no-right-answer problems. The ones where you have incomplete information and the consequences are irreversible.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/f33beb45-94f5-42fd-a5a7-47db74bb680d_357x522.jpeg&quot; alt=&quot;Cover of The Hard Thing About Hard Things by Ben Horowitz&quot; /&gt;&lt;/p&gt;
&lt;p&gt;For a technical founder, the hard thing is almost never the tech. The hard thing is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Picking one customer, one use case, one vertical, and ignoring everything else&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Building distribution before you feel ready&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Charging money before the product is “done”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Talking to the market more than you talk to your codebase&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The tech is the part you already know you can solve. That’s exactly why it’s the wrong place to start.&lt;/p&gt;
&lt;p&gt;If you’re a technical founder and you’ve spent two years on R&amp;amp;D with no product, no customers, and no distribution, you haven’t done the hard part.&lt;/p&gt;
&lt;p&gt;You’ve been avoiding it.&lt;/p&gt;
</content:encoded></item><item><title>GitHub Actions Pricing: The Platform Reality Check</title><link>https://julien.danjou.info/blog/github-actions-isnt-getting-greedy/</link><guid isPermaLink="true">https://julien.danjou.info/blog/github-actions-isnt-getting-greedy/</guid><description>GitHub Actions Pricing: The Platform Reality Check</description><pubDate>Thu, 18 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://resources.github.com/actions/2026-pricing-changes-for-github-actions/&quot;&gt;GitHub just announced pricing changes for GitHub Actions&lt;/a&gt;, and as expected, parts of the CI ecosystem panicked.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/41bef043-9868-48d4-a99c-bd9bf74df245_2582x946.webp&quot; alt=&quot;Screenshot of GitHub Actions pricing changes announcement&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Some people are celebrating the price drop on GitHub-hosted runners. Others are furious that GitHub will start charging for self-hosted runners. And a few businesses are suddenly asking existential questions.&lt;/p&gt;
&lt;p&gt;Since then, &lt;a href=&quot;https://github.com/orgs/community/discussions/182186&quot;&gt;GitHub has paused the self-hosted runner billing change&lt;/a&gt;, acknowledging they moved too fast and didn’t involve the ecosystem enough.&lt;/p&gt;
&lt;p&gt;That doesn’t change the underlying reality. It just delays the conversation.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;GitHub Actions Was Never “Free”&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;GitHub Actions is not just a binary that runs on your machines.&lt;/p&gt;
&lt;p&gt;It’s a platform:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;job queuing and scheduling&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;runner registration and lifecycle management&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;workflow orchestration&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;security, isolation, secrets handling&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;reliability at massive scale&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Even when you run your own hardware, GitHub is still doing a lot of work on your behalf. That infrastructure has always existed; hosted runners simply subsidized it.&lt;/p&gt;
&lt;p&gt;GitHub explicitly said it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“We have real costs in running the Actions control plane.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That’s not new. It’s just now being made explicit.&lt;/p&gt;
&lt;p&gt;Charging a small per-minute platform fee for self-hosted runners isn’t conceptually unfair: it’s GitHub aligning pricing with reality.&lt;/p&gt;
&lt;p&gt;If you believe this is unacceptable, there has always been a clear alternative: run Jenkins, GitLab CI, or any other system where you fully own the control plane.&lt;/p&gt;
&lt;p&gt;But you don’t get GitHub Actions “for free” just because the CPU cycles are yours.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Vendor Lock-In? Yes. And Everyone Chose It.&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Some people are suddenly discovering that GitHub Actions can lead to vendor lock-in.&lt;/p&gt;
&lt;p&gt;That’s… not new.&lt;/p&gt;
&lt;p&gt;GitHub Actions is a GitHub App deeply embedded in the GitHub ecosystem. YAML workflows, permissions, APIs, events. The lock-in was the trade-off for convenience, reliability, and speed of adoption.&lt;/p&gt;
&lt;p&gt;And let’s be honest: most teams are perfectly happy with vendor lock-in (right up until pricing becomes visible). You can’t have a deeply integrated platform &lt;strong&gt;and&lt;/strong&gt; complain when the platform prices itself like one.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/33178faf-02e9-447c-9284-33371515b8c8_1376x864.png&quot; alt=&quot;Illustration of vendor lock-in as a trade-off for platform convenience&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The Real Problem: CI Cost Arbitrage&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The real pain isn’t for users. It’s for companies whose business model is essentially:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“We’ll run GitHub Actions cheaper than GitHub.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That model was always fragile. If you are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;buying cloud compute from AWS, GCP, or another provider&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;reselling CI minutes&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;competing on price against &lt;strong&gt;Microsoft + Azure&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You are not competing on technology. You are competing on &lt;strong&gt;arbitrage&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;And arbitrage disappears the moment the platform owner decides to price closer to cost, or decides they don’t want that game played anymore.&lt;/p&gt;
&lt;p&gt;This is not new. This is how platforms work.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;When Self-Hosting Still Makes Sense&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Self-hosted runners absolutely still make sense when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;you are very large&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;you have predictable workloads&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;you already operate infra at scale&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;growth is slow, and margin optimization matters more than velocity&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In other words, when infrastructure &lt;em&gt;is&lt;/em&gt; your business or a stable internal cost.&lt;/p&gt;
&lt;p&gt;But for growing startups, optimizing CI costs too early is usually a mistake. Time spent shaving a few cents off a CI minute is time not spent shipping product.&lt;/p&gt;
&lt;p&gt;(And yes: &lt;a href=&quot;https://julien.danjou.info/p/why-engineers-shouldnt-decide-your&quot;&gt;this is precisely why engineers should not be the sole decision-makers on infra strategy&lt;/a&gt;.)&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;What GitHub’s Pause Actually Signals&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;GitHub’s follow-up message is important:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;They acknowledged real platform costs&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;They admitted poor communication&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;They paused to listen, not to abandon the direction&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is not GitHub “giving up.” It’s GitHub realizing that CI/CD has become &lt;strong&gt;critical infrastructure&lt;/strong&gt;, and changes must be introduced with more ecosystem buy-in.&lt;/p&gt;
&lt;p&gt;Hosted runners still get cheaper. Actions is still being positioned as a core execution layer (including for agentic workloads). The platform direction hasn’t changed.&lt;/p&gt;
&lt;p&gt;Only the timeline has.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The Takeaway&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;GitHub Actions isn’t “turning evil.” It’s finishing its transition from a feature to a platform.&lt;/p&gt;
&lt;p&gt;If your CI strategy depends on GitHub never charging for orchestration, scheduling, and reliability, that was never a safe assumption.&lt;/p&gt;
&lt;p&gt;And if your business depends on undercutting a hyperscaler on compute, you were always racing the clock.&lt;/p&gt;
&lt;p&gt;For everyone else, this remains mostly good news:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;clearer economics&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;cheaper hosted runners&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;a stronger, more explicit platform contract&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And a reminder that CI/CD is not just about cost. It&apos;s about leverage — whether that&apos;s &lt;a href=&quot;https://julien.danjou.info/blog/the-challenges-of-merge-queues&quot;&gt;merge queues that keep your main branch green&lt;/a&gt; or cheaper runners that let you ship faster.&lt;/p&gt;
</content:encoded></item><item><title>The Future Is Being Built Elsewhere</title><link>https://julien.danjou.info/blog/the-future-is-being-built-elsewhere/</link><guid isPermaLink="true">https://julien.danjou.info/blog/the-future-is-being-built-elsewhere/</guid><description>Why I’m worried and why founders can’t afford to wait for Europe to wake up.</description><pubDate>Wed, 26 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I read &lt;a href=&quot;https://blog.separateconcerns.com/2025-11-21-inexorable-progress.html&quot;&gt;Pierre Chapuis’ post&lt;/a&gt; &lt;em&gt;&lt;a href=&quot;https://blog.separateconcerns.com/2025-11-21-inexorable-progress.html&quot;&gt;Inexorable Progress&lt;/a&gt;&lt;/em&gt; last week, and a line stuck with me:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“You cannot stop the flow of progress. You can only decide to be an innovator, an early adopter, or a laggard.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;He’s right. And if you work in tech in Europe, you feel it every day: in the conversations, in the pace, in the mindset, in the decisions people around you consider “reasonable.”&lt;/p&gt;
&lt;p&gt;I live in France. I build a global product. I talk to US companies daily. And honestly?&lt;/p&gt;
&lt;p&gt;I’m worried too.&lt;/p&gt;
&lt;p&gt;Not because we lack talent. We don’t.&lt;/p&gt;
&lt;p&gt;Not because we lack engineers. We don’t.&lt;/p&gt;
&lt;p&gt;But because we lack the mental model required to compete in the world we’re entering. And the gap is accelerating.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/bb60df15-61dd-4dc5-8c10-fe3783d919ac_1376x864.webp&quot; alt=&quot;Illustration of the growing technology gap between Europe and the US and China&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;We Think We’re in the Same Race. We’re Not.&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;When I look at what’s happening in the US and China in AI, SaaS, robotics, automation… it feels like watching a different timeline.&lt;/p&gt;
&lt;p&gt;They’re scaling models that can refactor codebases.&lt;/p&gt;
&lt;p&gt;They’re shipping companies that go from idea to revenue in weeks.&lt;/p&gt;
&lt;p&gt;They’re pushing robotics into homes.&lt;/p&gt;
&lt;p&gt;They’re pouring capital at a pace that dwarfs what Europe raises in a quarter.&lt;/p&gt;
&lt;p&gt;Meanwhile, in France:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;We think regulation is a moat.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We believe “solving the French market” is a global strategy.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We look at the US and assume “we’ll catch up later.”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We treat AI like a temporary trend we can ignore until it stabilizes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This isn&apos;t a mindset gap. It&apos;s a timeline gap. &lt;a href=&quot;https://julien.danjou.info/blog/ai-feels-like-1999-all-over-again&quot;&gt;AI feels like 1999 all over again&lt;/a&gt; — the behavioral divide between adopters and holdouts is already compounding. And Europe is overwhelmingly on the wrong side.&lt;/p&gt;
&lt;p&gt;Europe is acting like it has &lt;em&gt;time&lt;/em&gt;. It doesn&apos;t.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/bc31cdd9-323f-4c0d-990a-f503c1d9ff87_1376x864.png&quot; alt=&quot;Illustration of Europe falling behind in the global tech race&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The Most Dangerous Bias: Thinking France Is the World&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;When I hear founders say:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“We’ll win the French market first.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I always think: &lt;em&gt;France is 0.8% of the world’s population.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;0.8%.&lt;/p&gt;
&lt;p&gt;China is 20× bigger. The US tech market is 10× bigger.&lt;/p&gt;
&lt;p&gt;The next wave of software will not be built for 0.8%.&lt;/p&gt;
&lt;p&gt;If your plan is to build only for France, culturally, financially, technically, you’ve already chosen to lose.&lt;/p&gt;
&lt;p&gt;Not because you’re bad.&lt;/p&gt;
&lt;p&gt;But because you’re playing a local game while everyone else is playing planetary-scale chess.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The Mindset Problem Nobody Talks About&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Here’s the part that founders and engineers will immediately recognize:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Most people here fundamentally don’t understand ROI, time, capital, or scale.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;They understand tasks. They understand constraints. They understand regulation.&lt;/p&gt;
&lt;p&gt;But they don’t understand leverage.&lt;/p&gt;
&lt;p&gt;They want to “optimize costs” when the problem is growth.&lt;/p&gt;
&lt;p&gt;They want to “avoid risk” when the problem is irrelevance.&lt;/p&gt;
&lt;p&gt;They want to “comply first” when the problem is competing at all.&lt;/p&gt;
&lt;p&gt;This is why hiring is more complex here, why product velocity is slower. Why teams hesitate on AI adoption, just as they did with cloud in 2008.&lt;/p&gt;
&lt;p&gt;It’s not a technology gap.&lt;/p&gt;
&lt;p&gt;It’s a worldview gap.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;We’re Living Like a Rich Country Without Creating Enough Value&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;This part is uncomfortable, but founders feel it viscerally.&lt;/p&gt;
&lt;p&gt;For 50 years, France has lived on increasing debt and the assumption that we can keep funding our lifestyle without producing equivalent value.&lt;/p&gt;
&lt;p&gt;But look at our major industries:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Our car industry is fighting Brussels just to be allowed to sell pollution past 2035, not to compete.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Our energy leadership was squandered by 20 years of indecision.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Our tech ecosystem celebrates being five years behind the US, as long as it’s “sovereign.”&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If we stop exporting cars, software, tech, heavy industry, how do we pay for everything?&lt;/p&gt;
&lt;p&gt;How do we fund innovation? How do we stay competitive?&lt;/p&gt;
&lt;p&gt;We don’t.&lt;/p&gt;
&lt;p&gt;We shrink.&lt;/p&gt;
&lt;p&gt;We tax more.&lt;/p&gt;
&lt;p&gt;We lose ground.&lt;/p&gt;
&lt;p&gt;And we pretend everything is fine.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Dropping Out of the Race Isn’t Ethical — It’s Surrender&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;When Pierre wrote:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“If you slow down, you are simply letting those who do not care about these issues in the first place win.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That hit me hard.&lt;/p&gt;
&lt;p&gt;Because this is the mindset I see too often in Europe:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“We shouldn’t build this.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“We should regulate it.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“We should wait until we’re sure.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“We should be cautious.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Caution is fine.&lt;/p&gt;
&lt;p&gt;Except when you’re in a race you didn’t choose but cannot opt out of. You don’t get to be “ethical” by refusing to play.&lt;/p&gt;
&lt;p&gt;You just hand the steering wheel of the future to people who don’t share your ethics.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;What Founders Should Take Away&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;I’m not writing this to doomscroll. I’m writing this because founders and engineers need to hear one thing:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Build globally. Don’t wait for permission.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The world is not waiting for Europe to catch up. The next decade will be brutal for anyone playing local games. Whether we like it or not, the next wave of innovation will be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;AI-native&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;global from day one&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;capital-efficient&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ruthlessly fast&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;engineered by people who want to win, not just exist&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And we can be part of that — if we choose to.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/7410d5e4-77e8-43ae-bd83-6cb4385e7888_1376x864.png&quot; alt=&quot;Illustration of founders building globally without waiting for permission&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Closing&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;I love France. I live here. My kids grow up here. But love doesn’t blind me.&lt;/p&gt;
&lt;p&gt;I see the same thing Pierre sees:&lt;/p&gt;
&lt;p&gt;A continent with world-class talent… and a mindset preventing it from playing the actual game.&lt;/p&gt;
&lt;p&gt;I don’t have the solutions. But I see the problems clearly. And as entrepreneurs, our best chance isn’t waiting for a savior.&lt;/p&gt;
&lt;p&gt;It’s building, ambitiously, globally, unapologetically, before the gap becomes irreversible. Because the world is moving.&lt;/p&gt;
&lt;p&gt;And this time, if we hesitate, we’ll be spectators. Not players.&lt;/p&gt;
</content:encoded></item><item><title>AI feels like 1999 all over again</title><link>https://julien.danjou.info/blog/ai-feels-like-1999-all-over-again/</link><guid isPermaLink="true">https://julien.danjou.info/blog/ai-feels-like-1999-all-over-again/</guid><description>AI feels like 1999 all over again</description><pubDate>Thu, 06 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last week, I spent two days with an old friend. We’ve known each other for fifteen years. He’s curious, a bit of a geek, but not “in tech.” He doesn’t use GPT. His wife doesn’t either. They’ve heard of AI the way you hear of a new restaurant: name recognition, no bookings.&lt;/p&gt;
&lt;p&gt;We talked, we cooked, we compared notes on work. At some point, I realized we were living on different planets. Not values. &lt;em&gt;Toolchains.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;He does great work. But AI just… isn’t part of his day. Meanwhile, I use it constantly: as a writing partner for emails, a sounding board for product decisions, a junior PM, a marketing intern who never sleeps. It’s not magic. It’s just leverage. And it reminds me of when I got internet access twenty-five years ago and people said, “Why would you need that every day?”&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/fd150c11-ef6a-4d93-9030-6b450cd31166_2752x1728.png&quot; alt=&quot;Illustration comparing AI adoption today to early internet adoption in 1999&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Two decades later, we answer that question by reflex, usually from a phone.&lt;/p&gt;
&lt;p&gt;I don’t say this to flex. I say it because the gap is already visible.&lt;/p&gt;
&lt;p&gt;If I compare my work today to two years ago, I’m doing two to three times more with better output. Same hours. Less context switching. I can hold more of Mergify’s product in my head, ship faster, and still write the marketing we used to split across two people. I wouldn’t claim I replace a whole team (let’s keep our illusions calibrated) but one founder plus AI now feels like one founder plus a sharp apprentice who learns absurdly fast.&lt;/p&gt;
&lt;p&gt;And I’m still only scratching the surface. There are tasks I &lt;em&gt;should&lt;/em&gt; automate that I haven’t, because of the classic XKCD curve: spending an hour to save a minute. The ROI is real; the overhead is too. It will get smoothed out, like everything else that starts out lumpy.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/19bd79ef-85b2-4abe-aa9a-4ff32df327d0_550x230.png&quot; alt=&quot;XKCD 974 comic about the time trade-off of automating tasks&quot; /&gt;
&lt;em&gt;XKCD 974&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;What’s striking is not just the productivity jump. It’s the new &lt;strong&gt;behavioral divide&lt;/strong&gt;. Twenty years ago the divide was access: who had broadband and who didn’t. Today the divide is adoption: who’s willing to put these systems in the loop every day, and who keeps them at arm’s length.&lt;/p&gt;
&lt;p&gt;Same laptop. Same calendar. Wildly different output.&lt;/p&gt;
&lt;p&gt;This isn’t about “AI replacing jobs.” It’s about &lt;strong&gt;AI reorganizing work&lt;/strong&gt; around people who are willing to collaborate with it. The difference between “I don’t see the point” and “this is in my daily loop” already compounds in quiet ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The email you write in 7 minutes instead of 27.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The product spec with five explored options instead of two.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The marketing page that results from testing three angles instead of arguing for one.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The code you ship because the blank page wasn’t blank.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Multiply that by days, then by years. That is how careers and companies diverge. And once AI starts creating content at scale, not just assisting — &lt;a href=&quot;https://julien.danjou.info/blog/the-synthetic-wave-is-already-here&quot;&gt;the synthetic wave is already here&lt;/a&gt; — the gap widens even faster.&lt;/p&gt;
&lt;p&gt;Of course, there are limits. AI isn’t judgment. It won’t hold your ethics, defend your taste, or choose your strategy. You still have to decide what “good” means, define constraints, and call the trade-offs. If you outsource your thinking, you don’t get leverage: you get noise.&lt;/p&gt;
&lt;p&gt;But if you keep the steering wheel, the car is very fast.&lt;/p&gt;
&lt;p&gt;There’s also a cultural point I didn’t expect: &lt;strong&gt;the stigma of using help&lt;/strong&gt;. Some people still think “real work” means doing everything yourself. Same energy as hand-writing HTML in 2003 to prove you’re serious.&lt;/p&gt;
&lt;p&gt;This reminds me of &lt;a href=&quot;https://2lr.substack.com/p/vive-la-france-long-live-the-us&quot;&gt;the latest post from Jean de La Rochebrochard&lt;/a&gt; where he talked about how French people are all about &lt;em&gt;crafting&lt;/em&gt;. No wonder AI adoption is going to be a long road here.&lt;/p&gt;
&lt;p&gt;But the craft isn’t in suffering; it’s in outcomes. Tools are honest if your goals are.&lt;/p&gt;
&lt;p&gt;I don’t know precisely what the next twenty years look like. I do see the pattern. Early on, new technology looks optional, even irrelevant. Then someone quietly uses it to do three times more with the same time. Then we call it table stakes. The people who adopted early won’t be smarter; they’ll just have trained their reflexes sooner.&lt;/p&gt;
&lt;p&gt;If you’re already all-in, you don’t need my sermon. If you’re AI-curious but unconvinced, try this: pick one workflow that hurts: a weekly email, a product spec, a marketing outline. Put an AI in the loop for a week. Not as a demo. As a colleague. Give it context. Ask for alternatives, not answers. Keep the steering wheel.&lt;/p&gt;
&lt;p&gt;If after seven days it doesn’t save you time &lt;em&gt;and&lt;/em&gt; improve your output, fine: ignore it for another year. But my bet is you’ll feel the old dial-up-to-broadband moment: once you touch the speed, it’s hard to go back.&lt;/p&gt;
&lt;p&gt;Back in Toulouse, my friend and I didn’t resolve anything. We just noticed the split. Same age. Same curiosity. Different daily habits. Twenty-five years ago the web felt optional right up until it didn’t. I think we’re there again. The storm isn’t coming. We’re already in the rain. You can stay dry for a while. Or you can learn to dance in it.&lt;/p&gt;
</content:encoded></item><item><title>42 Lessons at 42</title><link>https://julien.danjou.info/blog/42-lessons-at-42/</link><guid isPermaLink="true">https://julien.danjou.info/blog/42-lessons-at-42/</guid><description>Reflections on building, leading, learning, and staying sane along the way.</description><pubDate>Fri, 24 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Turning 42 felt like a good time to pause and reflect.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/8e725e25-cab4-4bc3-a763-0605db15e157_1356x837.webp&quot; alt=&quot;Illustration of 42 lessons learned after 42 years of life&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Not on milestones or ARR charts. But on what actually matters after two decades of building, shipping, leading, and living.&lt;/p&gt;
&lt;p&gt;So if 42 is the answer to everything, here’s what I’ve learned so far 👇&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;🧍‍♂️ Life, growth &amp;amp; happiness&lt;/strong&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The definition of happiness changes with age. And that’s okay.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The more you learn, the more you realize how little you know.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Curiosity is infinite; boredom is optional.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can’t optimize life like you optimize code.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you chase success, it runs faster. If you enjoy the run, it slows down.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Peace beats growth. Every time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Wealth is freedom, not Ferraris.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Desire is a contract to be unhappy until you get what you want. Cancel it often.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run more. Read more. Scroll less.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sleep is the most underrated productivity hack on Earth.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;&lt;strong&gt;💼 Building, leading &amp;amp; learning&lt;/strong&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Startups are people, not ideas.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leading by example is the hardest thing you’ll ever do.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leadership is 80% conversations you didn’t want to have.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Hire slow. Fire fast. Then sleep well.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Focus is a superpower. Protect it like oxygen.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The first job of a leader is clarity. The second is repetition.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Nobody cares how much you know; they care how you make them feel.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Feedback given late is just resentment with a bow on it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The most complex skill for a CEO to learn is knowing when to remain silent.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can’t delegate judgment.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;&lt;strong&gt;⚙️ Building products &amp;amp; companies&lt;/strong&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Quality compounds like interest. So does mediocrity. (&lt;a href=&quot;https://julien.danjou.info/blog/why-we-still-care-about-quality&quot;&gt;Why we still care about quality&lt;/a&gt;.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Success is rarely a pivot; it’s iteration with taste.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Great engineers fix root causes. Average ones fix symptoms.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The perfect product doesn’t exist: the one you can evolve does.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Simplicity scales. Complexity invoices.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Don’t chase vanity metrics: chase user trust.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Code reviews are easy. People reviews aren’t.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Build systems, not heroics.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The best feature is one you can delete later.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tech debt is fine. Moral debt isn’t.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;&lt;strong&gt;🧩 Mindset &amp;amp; philosophy&lt;/strong&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The only person you need to impress is your future self.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Gratitude is the antidote to burnout.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Meditation isn’t for everyone. Long runs count.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can be kind and still have high standards.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It’s okay to be wrong, just not stubborn.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The right “no” beats a thousand “maybes.”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compromise kills clarity. Choose, don’t blend.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Every problem looks smaller after a good meal.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Don’t overthink purpose. Build, love, repeat.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Curiosity never retires.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;&lt;strong&gt;❤️ Family, friends &amp;amp; the long game&lt;/strong&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Family first. Always. The best way to disconnect is to be needed by your kids.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You can’t buy time, but you can decide how to spend it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At 42, I’ve realized something simple but not easy:&lt;/p&gt;
&lt;p&gt;Life isn’t a sprint to a finish line. It’s an ongoing debugging session. You fix a few things, break a few others, and hopefully push a slightly better version of yourself every year.&lt;/p&gt;
&lt;p&gt;The same applies to building a company, raising children, or simply trying to be a decent person. You never “ship it” and move on. You iterate. You learn. You acknowledge that the next release will likely also contain bugs. And that’s fine.&lt;/p&gt;
&lt;p&gt;For a long time, I thought success meant achieving things: a title, a product, a number in the bank, a milestone on a slide deck. But the more I live, the more I see that &lt;em&gt;peace&lt;/em&gt; is the real metric. The ability to wake up excited, to solve interesting problems, to spend time with people you love, and to go to bed proud.&lt;/p&gt;
&lt;p&gt;That’s it.&lt;/p&gt;
&lt;p&gt;At 20, I wanted to build great code.&lt;/p&gt;
&lt;p&gt;At 30, I wanted to build great products.&lt;/p&gt;
&lt;p&gt;At 40, I just want to build a great life.&lt;/p&gt;
&lt;p&gt;And the funny thing?&lt;/p&gt;
&lt;p&gt;It still takes the same skills: focus, iteration, and knowing when to stop optimizing.&lt;/p&gt;
&lt;p&gt;So if there’s one thing I’d tell my younger self, it’s this:&lt;/p&gt;
&lt;p&gt;Keep learning, keep building, but remember that there’s no final commit.&lt;/p&gt;
&lt;p&gt;You don’t get to “win” life. You just get to live it. Version by version.&lt;/p&gt;
</content:encoded></item><item><title>Building Features One Prompt at a Time</title><link>https://julien.danjou.info/blog/vibe-coding-a-feature-with-ai/</link><guid isPermaLink="true">https://julien.danjou.info/blog/vibe-coding-a-feature-with-ai/</guid><description>How I built Mergify’s new autoqueue in less than an hour a day </description><pubDate>Tue, 26 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few weeks ago, we released a new feature at Mergify: &lt;strong&gt;&lt;a href=&quot;https://changelog.mergify.com/changelog/autoqueue-option-for-queue-rules&quot;&gt;autoqueue&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;It automatically adds pull requests into the merge queue when they’re ready. No more custom automation rules, no more fiddling with YAML — it just works, straight from the merge queue settings.&lt;/p&gt;
&lt;p&gt;Here’s the kicker: I coded it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/c43c313d-fbb9-4d8e-b129-c9c5345667c0_1144x577.png&quot; alt=&quot;Screenshot of the Mergify autoqueue feature settings&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Yes, me. The CEO. The guy who hasn’t touched production code in years. The guy who usually spends his days on calls, not in GitHub.&lt;/p&gt;
&lt;p&gt;And I did it in less than an hour a day, over three weeks, with the help of AI.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Why I Even Tried This&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;I’ve used Copilot casually before (mostly autocomplete in Emacs), but this time I wanted to &lt;strong&gt;go all-in&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Why? Curiosity, mostly. And time constraints. As a CEO, I have close to zero time to code, and this feature wasn’t urgent. So I thought: why not see what happens if I vibe-code it with AI?&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;How It Worked&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The way I interacted with Claude 4 via GitHub Copilot was simple: I explained the feature like I’d explain it to my team in a product story. I added some technical constraints (“use unit tests, not functional ones”).&lt;/p&gt;
&lt;p&gt;Then I let the AI go.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/f4d4604e-a9c8-4638-a8c1-4eaddc7f2681_1376x864.webp&quot; alt=&quot;Illustration of coding with AI assistance, like coding blindfolded&quot; /&gt;
&lt;em&gt;It just felt like coding blindfolded.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It wrote the code. I tweaked less than 5% of it. Once it was done, I sent it for review. I pasted my coworkers’ review feedback back into it. It rewrote. I guided. It iterated.&lt;/p&gt;
&lt;p&gt;Did it nail it on the first try? No. Sometimes it forgot instructions. Sometimes it “lost context” after a few iterations and tried to reinvent the test setup it had already learned. That was frustrating — like explaining to a junior dev, except this junior dev has goldfish memory.&lt;/p&gt;
&lt;p&gt;But eventually, it worked. The code was merged. Released. In production. Done.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;What Surprised Me&lt;/strong&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;I only changed about &lt;strong&gt;5% of the lines&lt;/strong&gt; myself.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Nobody on the team noticed it was “AI-coded.”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It handled six years of legacy code surprisingly well.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Two years ago this wouldn’t have been possible — the progress is insane.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;strong&gt;What It Means&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;This isn’t about me playing engineer again for nostalgia. It’s about what’s coming.&lt;/p&gt;
&lt;p&gt;The quality and quantity bar is about to rise dramatically. AI isn’t just autocomplete anymore; it’s &lt;em&gt;co-construction&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;You can ship faster. You can tackle features you don&apos;t fully understand at the start. You can guide at a high level and let the AI grind the details. A few months later, I took this even further — to the point where &lt;a href=&quot;https://julien.danjou.info/blog/so-i-will-never-write-code-again&quot;&gt;I stopped writing code entirely&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But it also raises new challenges. For instance:&lt;/p&gt;
&lt;p&gt;How do juniors review AI-generated PRs?&lt;/p&gt;
&lt;p&gt;How do teams trust code written by something that forgets your instructions after 10 turns?&lt;/p&gt;
&lt;p&gt;(That’s probably another blog post.)&lt;/p&gt;
&lt;p&gt;For now, though, I’ll just say this:&lt;/p&gt;
&lt;p&gt;I vibe-coded a real feature into existence in less than an hour a day.&lt;/p&gt;
&lt;p&gt;It felt like cheating. And I’m amazed.&lt;/p&gt;
</content:encoded></item><item><title>The Em Dash Is Dead</title><link>https://julien.danjou.info/blog/the-em-dash-is-dead/</link><guid isPermaLink="true">https://julien.danjou.info/blog/the-em-dash-is-dead/</guid><description>And I Might Have Killed It</description><pubDate>Tue, 05 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I’ve always loved the em dash. It’s elegant. It’s useful. It lets you breathe in your writing—without having to deal with commas or (God forbid) parentheses.&lt;/p&gt;
&lt;p&gt;Ten years ago, I wrote a book. A real book. With my hands.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/4331ec9c-0180-4764-83c5-82b487b55dbb_373x464.png&quot; alt=&quot;Cover of Serious Python book&quot; /&gt;
&lt;em&gt;Serious Python&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Over 68,000 words—and 77 beautiful em dashes.&lt;/p&gt;
&lt;p&gt;I wasn’t counting then—only recently did I check. You know, just to see how robotic I might’ve accidentally been.&lt;/p&gt;
&lt;p&gt;Because now? Now the em dash is a red flag.&lt;/p&gt;
&lt;p&gt;A decade ago, it was just a punctuation mark. Today, it’s basically a biometric marker for ChatGPT. Type an em dash on the internet in 2025, and someone will immediately side-eye your prose like you’re a prompt engineer trying to slip one past them.&lt;/p&gt;
&lt;p&gt;“Nice try, OpenAI.”&lt;/p&gt;
&lt;p&gt;Somehow, without even trying, I joined the ranks of the suspicious. My past self—the one tapping away joyfully, dashing away without care—was unknowingly building a future case against me.&lt;/p&gt;
&lt;p&gt;So here I am. A human. Who’s written thousands of human words. Who once thought the em dash was peak form—and now has to ask:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Am I even allowed to use it anymore?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The tragedy is this: AI didn&apos;t invent the em dash. &lt;em&gt;We&lt;/em&gt; gave it the em dash. We trained it on our books, our blog posts, our essays. We fed it so much em dash-laced content that now it thinks it&apos;s just what humans do. And to be fair… it &lt;em&gt;was&lt;/em&gt;. It&apos;s just one more way AI is reshaping how we communicate — and as &lt;a href=&quot;https://julien.danjou.info/blog/the-collapse-of-social-platforms&quot;&gt;social platforms collapse&lt;/a&gt; under synthetic content, even punctuation becomes a trust signal.&lt;/p&gt;
&lt;p&gt;Now, AI refuses to stop.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/@brentcsutoras/the-em-dash-dilemma-how-a-punctuation-mark-became-ais-stubborn-signature-684fbcc9f559&quot;&gt;You can threaten it, prompt it, scold it—“no more em dashes!”—and two lines later? Bam. Another one.&lt;/a&gt; It’s like trying to get your dog to stop barking at squirrels. It hears you. It just doesn’t care.&lt;/p&gt;
&lt;p&gt;Meanwhile, actual humans are uninstalling their em dash keyboard shortcuts. Coders are deleting — from their HTML snippets. Writers are rephrasing perfectly good sentences just to avoid looking synthetic.&lt;/p&gt;
&lt;p&gt;We didn’t lose a punctuation mark. We lost a friend.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/9eab604c-45bd-4567-82c8-5714f6a8c127_1376x864.webp&quot; alt=&quot;Illustration of the em dash being abandoned by human writers due to AI overuse&quot; /&gt;&lt;/p&gt;
&lt;p&gt;So, if you see an em dash in my writing—don’t panic.&lt;/p&gt;
&lt;p&gt;It’s not a bot. It’s just me. Old-school. Nostalgic. Typing with trembling fingers and a tear in my eye.&lt;/p&gt;
&lt;p&gt;Still human.&lt;/p&gt;
&lt;p&gt;Still grieving.&lt;/p&gt;
&lt;p&gt;Still em-dashing.&lt;/p&gt;
</content:encoded></item><item><title>The Synthetic Wave Is Already Here</title><link>https://julien.danjou.info/blog/the-synthetic-wave-is-already-here/</link><guid isPermaLink="true">https://julien.danjou.info/blog/the-synthetic-wave-is-already-here/</guid><description>How Spotify just confirmed the AI content tsunami I predicted.</description><pubDate>Tue, 29 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Six months ago, I wrote a blog post titled &lt;em&gt;“&lt;/em&gt;&lt;a href=&quot;https://julien.danjou.info/p/the-collapse-of-social-platforms&quot;&gt;The Collapse of Social Platforms&lt;/a&gt;&lt;em&gt;”&lt;/em&gt; At the time, it felt like a distant horizon — something you could see coming if you squinted into the future.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.theguardian.com/technology/2025/jul/14/an-ai-generated-band-got-1m-plays-on-spotify-now-music-insiders-say-listeners-should-be-warned?utm_source=chatgpt.com&quot;&gt;Spotify just made headlines for hosting an AI-generated “band”&lt;/a&gt; that racked up over a million plays before anyone realized the artists weren’t real. No humans. No guitars. Just prompts, algorithms, and a good understanding of how to feed the machine what people want to hear.&lt;/p&gt;
&lt;p&gt;And that’s just the beginning.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/f48377fe-ee4d-4444-85ae-95ea097789fa_1560x624.png&quot; alt=&quot;Screenshot of the AI-generated band on Spotify with over a million plays&quot; /&gt;
&lt;em&gt;Source&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;AI is Creating — Not Assisting&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Back then, I wrote that we were moving past “AI-assisted” content into “AI-native” creation. At the time, it might have sounded like theory. Now, we’ve entered the &lt;em&gt;Spotify Phase&lt;/em&gt;: platforms no longer just recommend content — they &lt;strong&gt;create it&lt;/strong&gt;. They don’t need to wait for artists to upload music. They can fill the catalog themselves.&lt;/p&gt;
&lt;p&gt;And they will.&lt;/p&gt;
&lt;p&gt;Because the economics are too good, the data feedback loops are too tight, and the audience — most importantly — doesn’t seem to care.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The Illusion of Authenticity is Enough&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Spotify didn’t advertise the AI band. It was just another artist profile. People listened. They added songs to playlists. They vibed. It’s only after the fact — after journalists started poking around — that we learned the truth.&lt;/p&gt;
&lt;p&gt;And you know what? Most listeners &lt;em&gt;still don’t care&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Which proves my original point: we’re not as attached to the &lt;em&gt;source&lt;/em&gt; of content as we think we are. We just want something that feels good, fits our mood, and plays seamlessly into our day. If that comes from a human or an LLM fine-tuned on hit-making formulas… who’s checking?&lt;/p&gt;
&lt;p&gt;This is the uncanny shift: content is becoming pure simulation. And for most, it’s indistinguishable from the real thing.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/81f81a63-5596-478a-8936-a4b0e961a236_1376x864.png&quot; alt=&quot;Illustration of synthetic content becoming indistinguishable from real&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Platforms are Optimizing Away People&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Spotify’s move is not an isolated event. It’s the canary in the coal mine for every content platform out there.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Why wait for a podcast to be recorded when you can prompt one into existence?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Why pay creators when you can generate infinite variations?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Why host unpredictable humans when you can manufacture predictable engagement?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;From AI-generated OnlyFans personas to YouTube clones to fake influencers on Instagram, we’re entering a phase where content isn’t created &lt;em&gt;by&lt;/em&gt; people — it’s created &lt;em&gt;for&lt;/em&gt; people by machines pretending to be people.&lt;/p&gt;
&lt;p&gt;It’s not a dystopia. It’s just a business decision.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;So Where does This Leave Us?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;If you’re a creator: The value of “real” is shifting. It may no longer be about production quality — but about human connection. Your face, your voice, your story might become the only proof-of-humanity people care about. Ironically, the more polished your content looks, the more people might question if &lt;em&gt;you&lt;/em&gt; made it.&lt;/p&gt;
&lt;p&gt;If you&apos;re a platform: Congratulations, you&apos;re entering the golden age of AI-powered margins. But beware the erosion of trust. Once users start doubting whether &lt;em&gt;anyone&lt;/em&gt; on your platform is real, &lt;a href=&quot;https://julien.danjou.info/blog/the-collapse-of-social-platforms&quot;&gt;the social glue breaks down fast&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you’re a user: Good luck. You’re about to be bombarded with synthetic everything. And the biggest risk isn’t being tricked — it’s not caring anymore whether what you’re consuming is real or not.&lt;/p&gt;
&lt;p&gt;That’s when the simulation wins.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;A Prediction, Revisited&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;In that original post, I wrote:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Real life will be the only place you’ll have left to interact with real humans.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I stand by it — even more today. The value of the human connection will rise in proportion to how rare it becomes online. Coffee with a friend. A live concert. A hand-written letter. These may become the luxury goods of the 2030s.&lt;/p&gt;
&lt;p&gt;So yes, the synthetic wave is here. But maybe that’s what we needed — a reason to remember what being human online really means.&lt;/p&gt;
&lt;p&gt;Until then: keep your eyes open, your ears sharp, and maybe… spend a little more time offline.&lt;/p&gt;
</content:encoded></item><item><title>The Day I Got Custom Table Legs</title><link>https://julien.danjou.info/blog/the-day-i-got-custom-table-legs/</link><guid isPermaLink="true">https://julien.danjou.info/blog/the-day-i-got-custom-table-legs/</guid><description>What It Taught Me About Support</description><pubDate>Tue, 22 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last week, I was with my team at our Mergify &lt;em&gt;on-site&lt;/em&gt; — what we call our &lt;em&gt;MAHOS (Mergify All-Hands On-Site)&lt;/em&gt;. Yes, we’re a fully remote team, so your regular off-site are called on-site for us. 😉&lt;/p&gt;
&lt;p&gt;We talked about the usual: roadmap, strategy, alignment. But then I brought up something a little different. I wanted to explain how we think about customer support at Mergify—not as a checkbox, not as a cost center, but as a way to deliver what I call the &lt;em&gt;&lt;a href=&quot;https://blog.mergify.com/how-we-handle-our-roadmap-for-mergify/&quot;&gt;wow effect&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;To make my point, I told them a story. A true one — about a table.&lt;/p&gt;
&lt;p&gt;Two years ago, I had just moved into a new house. My first real garden. I was excited to enjoy it, so I decided to buy a garden table and chairs. After browsing around, I picked &lt;a href=&quot;https://www.lafuma-mobilier.fr/&quot;&gt;Lafuma&lt;/a&gt; — a French brand I’ve liked since I was a kid.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/abbcdd6a-d840-42ed-80cf-7395e6ab1524_645x559.webp&quot; alt=&quot;Lafuma garden table product photo&quot; /&gt;
&lt;em&gt;Fun fact: my very first school backpack in first grade was a Lafuma. Yes, I still remember it.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Anyway, the table and chairs arrived. Great delivery. Good packaging. Quality seemed top-notch.&lt;/p&gt;
&lt;p&gt;But something felt… off.&lt;/p&gt;
&lt;p&gt;I sat down, and it wasn’t right. The proportions felt weird. The table was just a little too high, or the chairs too low. So I did what every curious engineer does: I grabbed a tape measure. Compared it to my indoor table. And there it was — the Lafuma table was exactly 2cm too tall. Just enough to make every meal feel slightly awkward.&lt;/p&gt;
&lt;p&gt;So I wrote them a message. Not angry, not demanding. Just a note saying:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Love the brand, love the product, but this feels like a design oversight.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I didn’t expect a reply.&lt;/p&gt;
&lt;p&gt;One week later, I got a call. It was someone from Lafuma’s support team — a QA engineer.&lt;/p&gt;
&lt;p&gt;He said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“I read your message. I’d like to understand exactly what’s wrong.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I explained. He listened. Then, without hesitation, he said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“Okay. I’ll send you custom table legs, 2cm shorter. You’ll have them next week.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I was stunned.&lt;/p&gt;
&lt;p&gt;“Wait, really? You can do that?”&lt;/p&gt;
&lt;p&gt;“Of course,” he replied. “We have spare legs in the workshop. We’ll just trim and ship them to your size.”&lt;/p&gt;
&lt;p&gt;And that’s exactly what happened. A week later, I swapped out the legs. Perfect fit. Perfect height. Perfect support.&lt;/p&gt;
&lt;p&gt;They didn’t have to do that. I wasn’t going to return the table. I wasn’t even asking for anything. But they did it anyway — because they cared. Because they listened. Because they understood what &lt;em&gt;great&lt;/em&gt; support means.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/52932582-97ff-4eda-93d4-56504d05d4e1_3024x3700.jpeg&quot; alt=&quot;Photo of the Lafuma table with custom-shortened legs installed&quot; /&gt;
&lt;em&gt;New leg size approved by my wife.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That’s the kind of service we try to deliver at &lt;a href=&quot;https://mergify.com&quot;&gt;Mergify&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Even in B2B, even in software, even at scale — you can still surprise people. (&lt;a href=&quot;https://julien.danjou.info/blog/why-we-still-care-about-quality&quot;&gt;Why we still care about quality&lt;/a&gt; is about the same mindset.) You can still make them feel heard. You can still &lt;em&gt;wow&lt;/em&gt; them. That’s what Amazon did so well for years. And it’s what so many companies forget as they grow.&lt;/p&gt;
&lt;p&gt;But it’s not optional. It’s the difference between a satisfied user and a loyal one. Between a customer and a fan.&lt;/p&gt;
&lt;p&gt;Build the table. And send the legs.&lt;/p&gt;
</content:encoded></item><item><title>The Problem with OKRs Isn’t OKRs</title><link>https://julien.danjou.info/blog/the-problem-with-okrs-isnt-okrs/</link><guid isPermaLink="true">https://julien.danjou.info/blog/the-problem-with-okrs-isnt-okrs/</guid><description>Why most teams would be better off with a clear plan than a quarterly ritual.</description><pubDate>Tue, 15 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I first encountered OKRs at &lt;a href=&quot;http://datadoghq.com&quot;&gt;Datadog&lt;/a&gt;. They were already in place when I joined — nobody really explained the “why” behind them. You just filled out your section in the shared Google Doc. The company was growing fast, already past 1000 employees. My team was new. We cargo-culted what others were doing.&lt;/p&gt;
&lt;p&gt;In theory, &lt;a href=&quot;https://en.wikipedia.org/wiki/Objectives_and_key_results&quot;&gt;OKRs&lt;/a&gt; are about aligning a team around measurable objectives. You set a direction (“Objective”), define how you’ll know you’ve made progress (“Key Results”), and track it. Simple. Ambitious. Inspiring, even.&lt;/p&gt;
&lt;p&gt;In practice? It was a glorified quarterly to-do list.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/7ac2d3e2-63c5-47c5-80b3-8f7a04943c51_1376x864.webp&quot; alt=&quot;Illustration of OKRs becoming a glorified quarterly to-do list&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Management would come down with a spreadsheet of tasks. You could discuss the list, maybe argue your way into trimming it down. But the measure of success wasn’t impact. It wasn’t even delivery velocity. It was binary: did we check the box, yes or no?&lt;/p&gt;
&lt;p&gt;There was no conversation about &lt;em&gt;why&lt;/em&gt; we were doing these things. No attempt to tie work to outcomes like activation, retention, support ticket volume, user satisfaction — anything. Product management was largely absent. Engineering was expected to execute. Period.&lt;/p&gt;
&lt;p&gt;That’s not OKRs. That’s project management theater with a quarterly cadence.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;When Metrics Become a Bludgeon&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Because impact could not be measured, the game became about managing perception. If your manager brought you a list of 10 items, the game was to negotiate down to 5 and deliver 6. Overdeliver by carefully managing the optics.&lt;/p&gt;
&lt;p&gt;It became a ritual of negotiated checklists, not shared purpose. A way to evaluate individuals, not steer teams. The illusion of alignment without any of the benefits.&lt;/p&gt;
&lt;p&gt;When we started &lt;a href=&quot;https://mergify.com/&quot;&gt;Mergify&lt;/a&gt;, I brought some of that skepticism with me.&lt;/p&gt;
&lt;p&gt;We did try OKRs — for a while. And in some areas, they worked. Marketing, for instance, benefitted from clear metrics and planning: support ticket volume, incident count, lead generation. Things we could measure and reflect on quarterly.&lt;/p&gt;
&lt;p&gt;But for product and engineering? Not so much. We didn’t have a mature enough product management function early on. And engineers — rightly — didn’t see the value in spending hours fine-tuning quarterly goals that wouldn’t actually guide their day-to-day. (We eventually &lt;a href=&quot;https://julien.danjou.info/blog/aligning-project-management-with&quot;&gt;evolved to a project-driven workflow&lt;/a&gt; that worked much better for us.)&lt;/p&gt;
&lt;p&gt;Eventually, we stopped.&lt;/p&gt;
&lt;p&gt;Not because we didn’t believe in planning or goals — but because the format had become more effort than it was worth. We weren’t getting leverage from the process.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Plans, Not Rituals&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;What I’ve come to believe is this: most teams would be better off writing down a plan than chasing OKR perfection.&lt;/p&gt;
&lt;p&gt;A good plan answers: what are we going to ship, why does it matter, and how will we know if it worked?&lt;/p&gt;
&lt;p&gt;That’s it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/1e3a42b9-4a90-4b47-a366-de399f172029_1376x864.webp&quot; alt=&quot;Illustration of writing plans instead of chasing OKR perfection&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I appreciated &lt;a href=&quot;https://newsletter.posthog.com/p/youre-doing-quarterly-planning-wrong&quot;&gt;how Posthog described this recently in one of their updates&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“In 2022, we required “OKRs” as part of quarterly planning, but eventually walked it back. We found engineers were agonizing over finding the right metrics, while also feeling like metrics didn&apos;t accurately reflect their subjective view of progress.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;They realized something important: even if you &lt;em&gt;do&lt;/em&gt; write OKRs, you still need to write the plan. So maybe just… start with the plan.&lt;/p&gt;
&lt;p&gt;Let OKRs emerge when they make sense — when you have a clear outcome to optimize for. But don’t let a framework become a crutch.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Success Isn’t a Template&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;We love templates in tech. We copy Google’s OKRs. Amazon’s memos. Netflix’s culture deck.&lt;/p&gt;
&lt;p&gt;But these practices only work when paired with deep understanding. Blindly copying them won’t align your team or 10x your output.&lt;/p&gt;
&lt;p&gt;Success isn’t a template. It’s clarity, judgment, and execution.&lt;/p&gt;
&lt;p&gt;Sometimes that means writing OKRs. Sometimes it just means writing a plan that everyone understands — and ships.&lt;/p&gt;
</content:encoded></item><item><title>AI Is a Human Interface Nightmare</title><link>https://julien.danjou.info/blog/ai-is-a-human-interface-nightmare/</link><guid isPermaLink="true">https://julien.danjou.info/blog/ai-is-a-human-interface-nightmare/</guid><description>AI Isn’t Broken, Our Expectations Are</description><pubDate>Tue, 08 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;For the last 80 years, computers have been calculators. Fancy ones, sure — with screens, keyboards, networks. But under the hood, they’re still just deterministic machines. You give them input, and they process it with logic gates and silicon, and they spit out the exact same output every time. That’s the deal. That’s the contract.&lt;/p&gt;
&lt;p&gt;And then came AI.&lt;/p&gt;
&lt;p&gt;AI doesn’t work like that. At all.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/e8e86a31-6b02-447d-b3d7-d7bbc06ae555_1376x864.png&quot; alt=&quot;Illustration of a computer interface struggling to represent AI&quot; /&gt;&lt;/p&gt;
&lt;p&gt;AI — especially large language models — are not deterministic. They’re a soup of probabilities and neural weights. When you talk to an AI, you’re not talking to a computer. You’re talking to something more like a human brain: a machine that guesses, infers, hallucinates, and sometimes nails it. And sometimes doesn’t.&lt;/p&gt;
&lt;p&gt;That’s fine. That’s expected. But the problem?&lt;/p&gt;
&lt;p&gt;AI still &lt;em&gt;runs&lt;/em&gt; on computers.&lt;/p&gt;
&lt;p&gt;The interface hasn’t changed. We’re still typing on keyboards, expecting precise answers. We’re still clicking buttons, expecting repeatability. But AI doesn’t think like that. And so the human-AI interface is totally broken.&lt;/p&gt;
&lt;p&gt;Ask ChatGPT “What’s the height of the Eiffel Tower?” and you might get the right number. Or not. And when it’s wrong, people freak out — “How can it not know that?” But think about it: the model is 1TB in size. It fits on a USB stick. You really believe all of humanity’s verified data fits in your pocket?&lt;/p&gt;
&lt;p&gt;It&apos;s not Google. It&apos;s not Wikipedia. It&apos;s a brain. A tiny, weird, synthetic brain that talks to you via a command-line interface and autocomplete. And if we figure out the interface problem, AI could actually &lt;a href=&quot;https://julien.danjou.info/blog/connecting-the-dots-with-ai&quot;&gt;connect the dots&lt;/a&gt; in ways humans never could.&lt;/p&gt;
&lt;p&gt;That’s the real nightmare: the medium is lying about the message.&lt;/p&gt;
&lt;p&gt;We call them “smartphones” because we used to make calls with them — even though calling is now maybe 1% of what we do. The name stuck. And maybe we’ll keep talking to AI through keyboards and chatboxes. But eventually, we’ll need new metaphors. New expectations. New ways to interact.&lt;/p&gt;
&lt;p&gt;Because what’s coming isn’t a better calculator.&lt;/p&gt;
&lt;p&gt;It’s something else entirely.&lt;/p&gt;
</content:encoded></item><item><title>Not Everything is a Hustle</title><link>https://julien.danjou.info/blog/not-everything-is-a-hustle/</link><guid isPermaLink="true">https://julien.danjou.info/blog/not-everything-is-a-hustle/</guid><description>There’s more than one way to be a founder.</description><pubDate>Tue, 01 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This Sunday, I made the mistake of opening LinkedIn.&lt;/p&gt;
&lt;p&gt;Among the usual weekend calm, a post caught my eye. A young founder proudly explaining how every morning before heading to the office, he cold-DMs 15 people. Every day. Rain or shine.&lt;/p&gt;
&lt;p&gt;He then listed all the &lt;em&gt;amazing&lt;/em&gt; outcomes of this habit: high-paid jobs, startup funding, conference invites, customer meetings, top hires, you name it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/3e474bba-e76d-4c80-8a3c-df3715f838e0_514x575.png&quot; alt=&quot;Screenshot of a LinkedIn post about cold-DMing 15 people every morning&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I couldn’t help but roll my eyes.&lt;/p&gt;
&lt;p&gt;So I reshared their post, wrote this instead, and published it on LinkedIn:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Every morning before I head to my home office,&lt;br /&gt;
I bring my kids to school.&lt;br /&gt;
It’s literally the single most important habit I’ve built.&lt;br /&gt;
Want to learn how I do it?&lt;br /&gt;
Comment “school” and I’ll tell you my secret.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Boom. &lt;em&gt;Thousands&lt;/em&gt; of views.&lt;/p&gt;
&lt;p&gt;Because here’s the truth: not everyone is under 30, lives in San Francisco, works at a startup with no users that raised millions, and spends their weekends writing cold emails to VCs and their evening eating pizzas with the team, crushing the latest release.&lt;/p&gt;
&lt;p&gt;Some of us are in our 40s.&lt;/p&gt;
&lt;p&gt;Some of us are building real companies.&lt;/p&gt;
&lt;p&gt;Some of us are taking our kids to school, going for a run at lunch, playing music on the weekends, and still—yes, still—shipping, raising, hiring, and growing.&lt;/p&gt;
&lt;p&gt;We don’t brag about skipping meals or sleeping 4 hours.&lt;/p&gt;
&lt;p&gt;We don’t show our productivity hacks on a treadmill desk.&lt;/p&gt;
&lt;p&gt;We don’t post about cold-DMing 15 people a day.&lt;/p&gt;
&lt;p&gt;We just don’t need to turn every moment into content.&lt;/p&gt;
&lt;p&gt;So if LinkedIn makes you feel like you’re not doing enough, or not doing it right, just know this:&lt;/p&gt;
&lt;p&gt;You’re not alone. You’re not late.&lt;/p&gt;
&lt;p&gt;And success can look very, very different.&lt;/p&gt;
&lt;p&gt;Take a breath. Pick up your kids. Go for that run.&lt;/p&gt;
&lt;p&gt;Enjoy the ride.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/4c7aadec-2cb7-48d7-b67c-fbc88df0aacb_2998x3395.jpeg&quot; alt=&quot;Photo of a father and child enjoying a walk outdoors&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>Why We Still Care About Quality</title><link>https://julien.danjou.info/blog/why-we-still-care-about-quality/</link><guid isPermaLink="true">https://julien.danjou.info/blog/why-we-still-care-about-quality/</guid><description>Quality is slow, hard, and totally worth it</description><pubDate>Tue, 24 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I recently read &lt;a href=&quot;https://linear.app/blog/why-is-quality-so-rare&quot;&gt;Linear’s excellent blog post on why quality is so rare&lt;/a&gt;, and it resonated deeply with me. Craft, quality, care — these aren’t buzzwords. They’re a way of working, a way of thinking, and frankly, the only way I’ve ever known how to build things.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/d3224e88-3b48-4bbd-ad4c-04f364308e0d_809x394.png&quot; alt=&quot;Screenshot of Linear&apos;s blog post on why quality is so rare&quot; /&gt;
&lt;em&gt;Linear: Why is quality so rare?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For me, it started with &lt;a href=&quot;https://julien.danjou.info/blog/open-source-is-getting-used-to-death&quot;&gt;open source&lt;/a&gt;. When you put your code out in the open, you naturally want to make it good. Maybe even beautiful. I started more than 20 years ago, polishing my Debian packages, making sure they were clean, understandable, and useful. Later I poured that same mindset into building &lt;em&gt;&lt;a href=&quot;https://awesomewm.org&quot;&gt;awesomewm&lt;/a&gt;&lt;/em&gt;, striving to write the best C code I could — because that code was me, visible to anyone curious enough to look.&lt;/p&gt;
&lt;p&gt;Open source taught me that quality is not an accident. It’s a habit. And a commitment.&lt;/p&gt;
&lt;p&gt;Even though &lt;a href=&quot;https://blog.mergify.com/why-mergify-codebase-isnt-open-source-anymore-a-tale-of-growth-change-and-adaptation/&quot;&gt;Mergify is no longer open source&lt;/a&gt;, the ethos never left. We still build like our code is going to be read by thousands, because, well, it is at least read by our folks. Our team ships work we’re proud of. Whether we win a deal or not, it’s common to hear people tell us:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The quality of Mergify stands out.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That never gets old.&lt;/p&gt;
&lt;p&gt;I know I’m not alone in this. Mehdi, my cofounder, and I have been building together for over 15 years. It’s in our DNA: we hate mediocrity. We won’t ship something that we wouldn’t use ourselves — joyfully.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/c6432d0e-0b74-4b90-bbfa-b9f7e4b83516_1376x864.webp&quot; alt=&quot;Illustration of craftsmanship and quality in software engineering&quot; /&gt;&lt;/p&gt;
&lt;p&gt;That said, I’ve also seen the flip side. Back when I worked on &lt;a href=&quot;https://openstack.org&quot;&gt;OpenStack&lt;/a&gt;, a massive open-source project, there was a lot of code… and not always a lot of care. Many contributors came from companies that didn’t value quality — and it showed. Open source can be beautiful, but when it’s driven by quantity instead of pride, it becomes exhausting. I hated that part.&lt;/p&gt;
&lt;p&gt;Quality isn’t just aesthetic. It’s a business strategy. Linear nailed that in their post. When you build something that feels right — fast, polished, thoughtful — users notice. They stay. They tell others. We’ve seen this at Mergify: our growth has been fueled not just by features but by how those features feel to use.&lt;/p&gt;
&lt;p&gt;But quality is more than just a great UI or bug-free code.&lt;/p&gt;
&lt;p&gt;It’s also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A fast, reliable, intuitive product.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Clean code that enables long-term agility.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Thoughtful defaults and edge-case handling.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Being able to say “no” when something adds complexity without enough payoff.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Getting there isn’t easy. You need judgment — to know what’s worth doing and what can wait. That comes with experience and the humility to know you’ll never get everything right. We aim for 80/20, not 100/0. Sometimes that means leaving the last 20% for another day — or maybe never. Not because we don’t care, but because we care about the whole system staying healthy and fast.&lt;/p&gt;
&lt;p&gt;Quality isn’t free. But it pays back. In speed, trust, and joy.&lt;/p&gt;
&lt;p&gt;So yes, it’s a choice. One you make every day.&lt;/p&gt;
&lt;p&gt;You can take the shortcut, or you can make something that lasts.&lt;/p&gt;
&lt;p&gt;We still choose the latter.&lt;/p&gt;
</content:encoded></item><item><title>Why Engineers Shouldn’t Decide Your Cloud Strategy</title><link>https://julien.danjou.info/blog/why-engineers-shouldnt-decide-your/</link><guid isPermaLink="true">https://julien.danjou.info/blog/why-engineers-shouldnt-decide-your/</guid><description>Growth Is the Battlefield</description><pubDate>Tue, 17 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/8f63cdd6-a53f-45db-b373-6ad8a3ae3d86_1376x864.png&quot; alt=&quot;Illustration of engineers debating cloud vs bare metal strategy&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Every few months, a new wave of engineers proudly announces their exit from the cloud. “We’re going bare metal. Look at our savings!”.&lt;/p&gt;
&lt;p&gt;The thread goes viral. Everyone nods wisely.&lt;/p&gt;
&lt;p&gt;But here’s the truth: if you’re making infra decisions without thinking about your growth model, you’re optimizing the wrong thing.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The cloud is not a cost problem. It’s a scaling solution.&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Startups don’t pay AWS bills because it’s cheap. They pay because it gives them instant access to global infrastructure they couldn’t build or operate themselves, and arguable, they should not spend time building a team to operate it.&lt;/p&gt;
&lt;p&gt;If your business is growing 100% year over year, optimizing gross margin is not your first battle. Surviving growth is.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/fb575c3d-c68d-403a-b54f-f945fda2099d_1557x874.png&quot; alt=&quot;Datadog revenue growth chart showing 150x scale over 10 years&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Datadog has been in the cloud since day one. They’ve &lt;strong&gt;scaled revenue 150× over 10 years&lt;/strong&gt;. The cloud didn’t kill them. It enabled them. They did that while controlling and optimizing their gross margin, but also without spinning up a giant project to double them by leaving the cloud (yet). Why? Because they’re still (a little bit more slowly) growing.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Bare metal works — if you’re not growing much.&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://basecamp.com/cloud-exit&quot;&gt;Basecamp left AWS.&lt;/a&gt; They made noise. But they also “only” grew 6**×** in 12 years — not 150**×**. When growth is slow and predictable, you can (and should) optimize for margin. You have time. You have predictability. Maybe you even have ops engineers with spare cycles. And if you don’t, as you’re not struggling to grow your team, you can expand into infrastructure and internalize it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/5df8cdbb-31cc-4f5d-a20d-339a9bbbfd13_1673x503.png&quot; alt=&quot;Basecamp growth comparison showing 6x growth over 12 years&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When you run out of stamina for growth, you optimize your gross margin; therefore, your cost is what you want to shrink. It’s a different phase.&lt;/p&gt;
&lt;p&gt;The same goes for any small or internal project; there might be no need to deal with a cloud provider if you know your infrastructure will not double every year. Just rent or buy a bunch of bare metal servers and deal with them.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Most engineers don’t see the whole picture.&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;If you follow engineers, they always want to optimize. The problem is that most of them can’t optimize your market, your growth. The only thing they know how to optimize is resource consumption and cost by working more.&lt;/p&gt;
&lt;p&gt;Therefore, they’ll look at a line item on the AWS invoice and say, “We could get this cheaper with low-cost bare metal and our team spending time spinning things up.”&lt;/p&gt;
&lt;p&gt;Maybe.&lt;/p&gt;
&lt;p&gt;Who’s factoring in the cost of talent to manage infra? The time you won’t spend shipping product? The opportunity cost of slowing down? (This is a classic case of &lt;a href=&quot;https://julien.danjou.info/blog/solving-build-vs-buy&quot;&gt;solving build vs. buy&lt;/a&gt;.)&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;So… bare metal or cloud?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;It depends.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If you’re building a startup and aiming for fast growth: &lt;strong&gt;cloud, 100%.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you’re a slow-growth company with predictable traffic: &lt;strong&gt;maybe bare metal.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you’re a big org running an intranet or legacy app: &lt;strong&gt;buy servers, no big deal.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But let’s stop pretending this is just a technical decision.&lt;/p&gt;
&lt;p&gt;It’s not.&lt;/p&gt;
&lt;p&gt;It’s a strategic one.&lt;/p&gt;
</content:encoded></item><item><title>Marc Chagall Never Painted That</title><link>https://julien.danjou.info/blog/marc-chagall-never-painted-that/</link><guid isPermaLink="true">https://julien.danjou.info/blog/marc-chagall-never-painted-that/</guid><description>Or Why AI Isn’t Google</description><pubDate>Tue, 03 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It was a casual Friday. Nothing special—except I was on kid duty for lunch pickup, a rare detour in my usual routine.&lt;/p&gt;
&lt;p&gt;As we strolled home, baguette under one arm, my daughter told me about her morning in class.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/7116745f-2da6-4f95-8918-4eebaebe0fcc_1376x864.webp&quot; alt=&quot;Illustration of a parent and child walking home discussing art class&quot; /&gt;&lt;/p&gt;
&lt;p&gt;They had studied Marc Chagall. Her eyes sparkled as she recounted it, and then she asked if we could go see &lt;em&gt;La Fée Électricité&lt;/em&gt; next time we were in Paris.&lt;/p&gt;
&lt;p&gt;That name rang a bell, but I had no clue where that was exposed and if it was even in Paris. Painting is not my strong suit. Once home, I did what any responsible parent would do: I picked up my phone from my pocket and Googled it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/ac15cf29-2ff5-411d-bb51-2f91ffdda95b_805x325.jpeg&quot; alt=&quot;La Fee Electricite painting by Raoul Dufy&quot; /&gt;
&lt;em&gt;La Fée Électricité&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The first answer showed that the painting was exhibited in the &lt;em&gt;&lt;a href=&quot;https://www.mam.paris.fr/fr/oeuvre/la-fee-electricite&quot;&gt;Musée d’Art Modern de Paris&lt;/a&gt;&lt;/em&gt;. But I didn’t tell my daughter right away. As I was scrolling on my phone, something didn’t click.&lt;/p&gt;
&lt;p&gt;The museum mentioned that this painting was from Raoul Dufy — not Chagal.&lt;/p&gt;
&lt;p&gt;I triple-checked on the web and Wikipedia. The result was the same. &lt;em&gt;La Fée Électricité&lt;/em&gt; isn’t by Chagall at all. It’s really by Raoul Dufy.&lt;/p&gt;
&lt;p&gt;That’s when the realisation hit me. The mistake probably didn’t come from a textbook or even a hasty Wikipedia glance. No, my bet is the teacher asked ChatGPT (or Bard, or whatever the tool of the week is) to prepare her lesson. AI probably hallucinated the answer. And nobody caught it.&lt;/p&gt;
&lt;p&gt;We&apos;re at this weird moment where many people treat AI like it&apos;s a search engine. Or worse: as if it&apos;s a source of truth. And when this confidence gets applied at scale — to content, media, music — &lt;a href=&quot;https://julien.danjou.info/blog/the-synthetic-wave-is-already-here&quot;&gt;the synthetic wave is already here&lt;/a&gt;, and nobody is fact-checking it.&lt;/p&gt;
&lt;p&gt;It’s not. It’s a conversation partner with infinite confidence and a shaky grasp on facts.&lt;/p&gt;
&lt;p&gt;This isn’t a rant against AI. I use it daily and wouldn’t go back. But it’s a gentle reminder: if you don’t know how to question what it says—or double-check your sources—it’s easy to teach your whole class wrong facts.&lt;/p&gt;
&lt;p&gt;No big deal this time. My kid went back to school in the afternoon after I dared her to ask her teacher if Chagal was really the painter behind La Fée Électricité. She did ask, and the teacher corrected her mistake for the whole class and moved on.&lt;/p&gt;
&lt;p&gt;But next time, who knows?&lt;/p&gt;
</content:encoded></item><item><title>Security Starts Where Convenience Ends</title><link>https://julien.danjou.info/blog/security-theater-is-not-security/</link><guid isPermaLink="true">https://julien.danjou.info/blog/security-theater-is-not-security/</guid><description>The alarming state of security in too many tech companies</description><pubDate>Tue, 27 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Over the past quarter, I’ve had conversations with a handful of engineers working at French software companies — from early-stage startups to more established players. Companies with thousands of users and millions of euros of revenue.&lt;/p&gt;
&lt;p&gt;During the conversation, what struck me wasn’t what they were building or how they scaled. It was how little attention and seriousness many of them gave to security.&lt;/p&gt;
&lt;p&gt;Some of these companies handle critical user data. Others operate infrastructure that powers thousands of customers. Yet, their security posture often amounts to… vibes. A bit of MFA here. A few random VPNs there. But very little that would pass as security maturity by any professional standard.&lt;/p&gt;
&lt;p&gt;And yes — I get it. Security is not easy. It’s thankless. It doesn’t generate revenue. But here’s the deal: ignoring it isn’t neutral. It’s dangerous.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/b22f03ac-b154-4fc7-9883-19a60340d81c_1376x864.png&quot; alt=&quot;Illustration of poor security posture in tech companies&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;What’s going wrong?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;From what I’ve seen and heard:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No MDM (Mobile Device Management):&lt;/strong&gt; Engineers using unmonitored laptops, often their own machines, with no control over OS updates, disk encryption, or even if a password is required. The reason is sometimes that engineers are doing a heavy push back on this for convenience and have too much weight in the decision making process for security, without having a clue.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No endpoint visibility:&lt;/strong&gt; If a machine is compromised, there’s no way to know. Worse, there’s no way to &lt;em&gt;do anything about it&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No SOC 2, no ISO 27001, not even a roadmap:&lt;/strong&gt; These aren’t magic bullets, but they’re a minimum bar—a starting point. Yet many companies either dismiss them or postpone them indefinitely.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Weak privilege separation:&lt;/strong&gt; Developers with production access “just in case.” CI pipelines that can destroy environments. You get the picture.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This isn’t just a case of companies not being “mature enough.” This is willful neglect disguised as pragmatism.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/7e6bd99f-61a8-46b5-899a-b6299113c982_699x480.png&quot; alt=&quot;Illustration of developers resisting security measures for convenience&quot; /&gt;&lt;/p&gt;
&lt;p&gt;One of the reasons (and &lt;a href=&quot;https://julien.danjou.info/blog/why-engineers-shouldnt-decide-your&quot;&gt;why engineers shouldn&apos;t always decide your strategy&lt;/a&gt;): developers often act like divas. Many of them refuse to make even minor trade-offs in convenience for the sake of better security. They don’t want to lose their admin rights, install an MDM agent, or be told they can’t SSH into prod “just in case.” Security? That’s someone else’s problem—until it’s not. The bigger issue is that in many early-stage or engineering-led companies, devs hold disproportionate decision-making power, and there’s no one truly responsible for security. Without pushback, security becomes optional. This isn’t about lack of maturity. It’s about a complete lack of incentives, accountability, and understanding.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;“We’re not a target” is a myth&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Many of these teams believe they’re too small or irrelevant to be attacked. That might be true—until it’s not.&lt;/p&gt;
&lt;p&gt;In France, &lt;a href=&quot;https://www.france24.com/en/france/20250513-armed-gang-tries-to-kidnap-crypto-ceo-s-daughter-grandson-in-central-paris&quot;&gt;we’ve recently seen crypto entrepreneurs attacked physically&lt;/a&gt;. That’s one end of the spectrum. But digital attacks? They don’t need to be targeted. They can be opportunistic. You leave a port open, someone finds it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/e3040ae4-cdf9-41db-aeec-5b69b9e508f6_709x723.webp&quot; alt=&quot;Screenshot of article about armed gang attacking crypto entrepreneurs in Paris&quot; /&gt;
&lt;em&gt;Original Article&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Security hygiene isn’t about paranoia. It’s about respecting your customers, your users, and your own future.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;What should change?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;I’m not a security expert. I run a bootstrapped software company, and I’m just one of the gears in our security. But here’s what I’d like to see as a baseline across every SaaS company:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;MDM. For every laptop.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Disk encryption. Mandatory.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Admin access? Logged. Monitored. Reviewed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Least privilege policies by default.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CI/CD pipelines with auditable change control.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Security reviews baked into product releases.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A roadmap toward certifications like SOC 2 or ISO 27001.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not because they’re trendy, but because they force discipline.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Why I’m writing this&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Because I’m genuinely worried. I think we’re going to see more breaches, more leaks, more “oops we exposed production DBs for a month” stories. And when that happens, saying “well it was complicated” won’t cut it.&lt;/p&gt;
&lt;p&gt;Security is part of the job. It’s not an add-on. It’s not the CISO’s problem. It’s yours. It’s mine. It’s everyone’s.&lt;/p&gt;
&lt;p&gt;Let’s raise the bar.&lt;/p&gt;
</content:encoded></item><item><title>Why French Tech Is Playing Not to Lose</title><link>https://julien.danjou.info/blog/why-french-tech-is-playing-not-to/</link><guid isPermaLink="true">https://julien.danjou.info/blog/why-french-tech-is-playing-not-to/</guid><description>Stop Settling for the Crumbs</description><pubDate>Tue, 29 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;There’s an uncomfortable truth in French tech. One that doesn’t get talked about much in conferences or demo days. One that’s quietly baked into a lot of the strategies, funding rounds, and product roadmaps.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Most French tech entrepreneurs are not playing to win. They’re playing not to lose.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;They build nice, safe products. They avoid risk. They target the French market, maybe Europe if they’re feeling bold. They slap “sovereign” stickers on their infrastructure and call it innovation.&lt;/p&gt;
&lt;p&gt;But let’s be honest: that’s not innovation. It’s insurance.&lt;/p&gt;
&lt;p&gt;And it’s killing our chances to matter.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The Safety Bubble&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;A lot of French startups don’t dream of being global leaders. They don’t set out to compete with the best in the world. They design around constraints. Around regulation. Around what’s “acceptable.” Around what the French government might subsidize.&lt;/p&gt;
&lt;p&gt;And for a while, that works. You can build a decent company by playing it safe. Get a few French customers. Raise a seed round. Maybe land a grant from the government innovation bank. Position yourself as “GDPR-compliant,” “cloud sovereign,” “data hosted in Europe.”&lt;/p&gt;
&lt;p&gt;Yay. The state might give you a pat on the back. However, no CAC 40 company will buy your product (way too risky to work with you), but if you’re lucky, you’ll land a few scale-ups.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/3917b251-bb77-4b57-afe8-b9a982a1eeec_1376x864.png&quot; alt=&quot;Illustration of French tech startups playing it safe&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Meanwhile, somewhere in California, someone is building the product that your customers will switch to as soon as they need something that actually moves fast, scales globally, or breaks new ground.&lt;/p&gt;
&lt;p&gt;This is the real issue: &lt;strong&gt;French entrepreneurs are incentivized to build for compliance, not for users.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;And that’s how you end up with European companies that are five years behind their American counterparts, but proudly calling themselves “local alternatives.” They sometimes go as far as making other entrepreneurs feel guilty for using a non-French technology provider because it’s better — especially in those complicated times.&lt;/p&gt;
&lt;p&gt;That’s not ambition. That’s surrender in disguise.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Sovereignty Is Not a Product Strategy&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Let’s talk about the word that gets thrown around a lot lately: &lt;strong&gt;sovereignty&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Yes, it’s important that we have control over critical infrastructure. Yes, data security matters. But it’s not a business model. It’s not differentiation.&lt;/p&gt;
&lt;p&gt;You don’t win by saying “we’re like AWS, but French.”&lt;/p&gt;
&lt;p&gt;You don’t win by saying “we’re like OpenAI, but hosted in Europe.”&lt;/p&gt;
&lt;p&gt;You don’t win by saying “we’re like GitHub, but on OVH.”&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/205afe31-5195-4d6d-a651-fe9a05d5696c_1208x368.png&quot; alt=&quot;Screenshot of an email pitching a sovereign cloud alternative&quot; /&gt;
&lt;em&gt;An actual email I received today while writing this post.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You win by solving problems better. By innovating. By taking the risk to challenge the status quo.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sovereign hosting is a checkbox. It’s not a moat.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What frustrates me is that they use it as a crutch. As a justification for not competing with the leaders. As an excuse to stay in their comfort zone and call it strategy.&lt;/p&gt;
&lt;p&gt;Meanwhile, the world is moving. And they’re watching.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The US Is Still the Market to Beat&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;There’s a pattern I see all the time: French startups that refuse to even consider entering the US market.&lt;/p&gt;
&lt;p&gt;“It’s too crowded.”&lt;/p&gt;
&lt;p&gt;“It’s too expensive.”&lt;/p&gt;
&lt;p&gt;“We’ll start with France. Then maybe Germany. Then maybe UK.”&lt;/p&gt;
&lt;p&gt;But here’s the thing: &lt;strong&gt;the US is the only market that can validate your product at scale&lt;/strong&gt;. It’s where the fastest companies live. The most demanding customers. The strongest competitors.&lt;/p&gt;
&lt;p&gt;If you’re not building with the intention of competing there, what are you doing?&lt;/p&gt;
&lt;p&gt;Sure, it’s hard. Sure, you’ll probably get punched in the face. But that’s where you learn. That’s where you improve. That’s where you build something that matters globally — not just locally.&lt;/p&gt;
&lt;p&gt;Avoiding the US is not a cautious strategy. It’s a self-imposed ceiling.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;French Tech Has the Talent. What’s Missing Is the Fire.&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;I’m not saying we don’t have the brains. Or the skills. Or the creativity.&lt;/p&gt;
&lt;p&gt;We do.&lt;/p&gt;
&lt;p&gt;I’ve worked with incredible engineers, designers, product thinkers in France. People who could work anywhere in the world. (I wrote more about what it takes to build globally in &lt;a href=&quot;https://julien.danjou.info/blog/the-future-is-being-built-elsewhere&quot;&gt;The Future Is Being Built Elsewhere&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;But too often, the culture around them is one of caution, not ambition. One where failure is seen as shameful, not as a stepping stone. One where fundraising is seen as the end goal, not the fuel for building something bold.&lt;/p&gt;
&lt;p&gt;And when that’s the vibe — guess what? You get safe products. You get local clones. You get decks that talk more about “sovereignty” than about solving user problems in new and interesting ways.&lt;/p&gt;
&lt;p&gt;You don’t get industry leaders. You don’t get Snowflakes or Stripes or Notions.&lt;/p&gt;
&lt;p&gt;And that’s a shame. Because we could.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;We Need to Build Like We Mean It&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;If we want to matter in the next decade of tech, we need to stop building like we’re afraid.&lt;/p&gt;
&lt;p&gt;We need founders who want to win — not just survive.&lt;/p&gt;
&lt;p&gt;We need investors who back risky, ambitious plays — not just safe, incremental growth.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/59a2f8ca-3ab5-4f95-854c-94d31bfc8518_1376x864.png&quot; alt=&quot;Illustration of building ambitious tech products that target the global market&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We need products that &lt;em&gt;start&lt;/em&gt; by targeting the world’s best users — not just the few who care about local hosting.&lt;/p&gt;
&lt;p&gt;And yes, we’ll need to fail more. That’s part of the deal.&lt;/p&gt;
&lt;p&gt;But at least we’ll be failing forward. Not settling for second place.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Closing&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;I know this is an unpopular opinion. I know it sounds harsh. But I say this because I want us to do better.&lt;/p&gt;
&lt;p&gt;French tech doesn’t lack talent. It lacks urgency. It lacks the hunger to build something that doesn’t just exist — but leads.&lt;/p&gt;
&lt;p&gt;And until we face that, we’ll keep getting the crumbs.&lt;/p&gt;
</content:encoded></item><item><title>From Failure to Focus</title><link>https://julien.danjou.info/blog/from-failure-to-focus/</link><guid isPermaLink="true">https://julien.danjou.info/blog/from-failure-to-focus/</guid><description>How CI Insights Was Born</description><pubDate>Tue, 22 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Startups love to talk about iteration. Failing fast. Learning from mistakes. But when you’re six months into a project that doesn’t ship — not once, but &lt;em&gt;twice&lt;/em&gt; — that mantra starts to feel a bit too real.&lt;/p&gt;
&lt;p&gt;At Mergify, we recently spent almost a year building two separate products around CI/CD. Both had potential. Both looked promising. And both ended up in the graveyard.&lt;/p&gt;
&lt;p&gt;I have written about this in three posts already: &lt;a href=&quot;https://julien.danjou.info/blog/the-100000-mistake&quot;&gt;The $100,000 Mistake&lt;/a&gt;, &lt;a href=&quot;https://julien.danjou.info/blog/when-nobody-wants-your-product&quot;&gt;When Nobody Wants Your Product&lt;/a&gt;, and &lt;a href=&quot;https://julien.danjou.info/blog/when-great-tech-isnt-enough&quot;&gt;When Great Tech Isn&apos;t Enough&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But here’s the thing: that journey was the best thing that could’ve happened to us because it led to &lt;strong&gt;&lt;a href=&quot;https://mergify.com/product/ci-insights&quot;&gt;CI Insights&lt;/a&gt;&lt;/strong&gt;, our newest product, which we’re shipping for real this time.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;When R&amp;amp;D Goes Off-Road&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;If you’ve followed the first parts of this series, you know the backstory. In 2023, after years of growing Mergify’s Merge Queue product, we started exploring new ideas.&lt;/p&gt;
&lt;p&gt;The first was &lt;strong&gt;CI Optimizer&lt;/strong&gt; — a tool to help teams reduce CI/CD costs. But we quickly learned something important: engineers aren’t the ones who care about CI spend. That’s a FinOps conversation. And FinOps teams weren’t our users.&lt;/p&gt;
&lt;p&gt;Then came &lt;strong&gt;CI Issues&lt;/strong&gt;, a project aimed at tracking flaky tests and infrastructure problems. This time we had interest. Teams &lt;em&gt;did&lt;/em&gt; struggle with these problems. But we made the mistake of diving into R&amp;amp;D without doing proper design work. We built a complex system — and it worked — but it was so hard to deploy and operate that we never felt confident letting users in.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/66339157-7d29-4350-b790-71eda725ea58_1376x864.webp&quot; alt=&quot;Illustration of failed product attempts before finding the right design&quot; /&gt;
&lt;em&gt;No design, you said?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;So once again, we shelved it.&lt;/p&gt;
&lt;p&gt;Two product attempts, zero releases.&lt;/p&gt;
&lt;p&gt;But what survived both efforts was a deeper understanding of the pain engineers feel every day around CI.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The Real Problem: CI Is a Black Box&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Across all our conversations, one theme kept showing up: visibility.&lt;/p&gt;
&lt;p&gt;Teams weren’t desperate to reduce CI costs. They weren’t obsessed with infrastructure failures. But they were all asking the same questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Why is our CI pipeline so slow?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Which PRs are consistently the bottleneck?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Which tests are flaky?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Where are we wasting time?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nobody had good answers. CI is treated like a utility — flip the switch and hope the light turns on. But when it doesn’t, or when it flickers, most teams don’t have the tools to understand &lt;em&gt;why&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;We realized what was missing wasn’t a FinOps tool or a smart test tracker — it was &lt;strong&gt;observability&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;CI Insights: The Missing Layer&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;With all the groundwork we’d laid — our CI connectors, our data pipelines, our internal dashboards — we already had most of the pieces. We just needed to reframe the problem.&lt;/p&gt;
&lt;p&gt;So we did.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/dc14e464-b5af-41f0-8d1d-6f1ebbe334f2_2880x1920.png&quot; alt=&quot;Screenshot of the Mergify CI Insights dashboard&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CI Insights&lt;/strong&gt; is the observability layer for your CI.&lt;/p&gt;
&lt;p&gt;It helps teams:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Spot flaky jobs and tests&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Identify long-running or unstable jobs&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Understand where their pipeline is slowing them down&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Track trends over time across teams and repos&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s not about cost savings. It’s not about blaming the CI tool. It’s about &lt;em&gt;clarity&lt;/em&gt; — understanding what’s going on, so teams can ship faster and with less frustration.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;This Time, We Built It Right&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;We learned from our previous mistakes.&lt;/p&gt;
&lt;p&gt;This time, we:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Started with real use cases&lt;/strong&gt; from customers and our own internal needs&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Designed first&lt;/strong&gt;, coded second&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Focused on value over complexity&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Shipped it&lt;/strong&gt; to users. Yes. Really.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We’ve been using CI Insights ourselves for months now. It already helped us catch flaky jobs, detect broken test workflows, and reduce merge queue delays.&lt;/p&gt;
&lt;p&gt;Now, we’re rolling it out to early users — and so far, the feedback has been 🔥.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The Bigger Picture&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;CI Insights is more than just a tool. It’s a shift in how we think about CI.&lt;/p&gt;
&lt;p&gt;It’s not just a thing that “runs your tests.” It’s a critical part of your development workflow. And it deserves the same kind of visibility, metrics, and tooling that you already have for production systems.&lt;/p&gt;
&lt;p&gt;We’re building CI Insights to be the &lt;strong&gt;best observability tool for CI&lt;/strong&gt; — because engineers deserve better tools.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;What’s Next?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;We’re just getting started. The roadmap is full. We’re onboarding users slowly and shaping the product based on real feedback.&lt;/p&gt;
&lt;p&gt;If CI is a black box for your team — if you’re tired of guessing why things are slow — we’d love to hear from you.&lt;/p&gt;
&lt;p&gt;👉 &lt;a href=&quot;https://mergify.com/product/ci-insights&quot;&gt;Request early access&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>Not Just a Job, It’s a Ride</title><link>https://julien.danjou.info/blog/not-just-a-job-its-a-ride/</link><guid isPermaLink="true">https://julien.danjou.info/blog/not-just-a-job-its-a-ride/</guid><description>How we hire at Mergify</description><pubDate>Tue, 15 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Hiring is easily one of the hardest jobs I’ve had to do at &lt;a href=&quot;https://mergify.com&quot;&gt;Mergify&lt;/a&gt;. Not because there aren’t smart people out there — there are. But because we’re not just hiring for skill. We’re hiring for mindset.&lt;/p&gt;
&lt;p&gt;And let’s be honest: that’s a lot harder to screen for than technical chops.&lt;/p&gt;
&lt;p&gt;When you’re building a startup, every new hire changes the shape of the team. Every person matters. You’re not adding a cog to a big machine — you’re inviting someone on the ride with you, and they’d better be ready for the speed bumps.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;What We Actually Look For&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;At Mergify, we look for people who care. Not just about clean code or nice UI — but about the mission. The thing we’re building. The problems we’re solving. If what we’re doing doesn’t excite you, we’re not going to try to sell it to you. We want you to come in already leaning forward.&lt;/p&gt;
&lt;p&gt;We’re looking for people who aren’t title-driven but &lt;strong&gt;outcome-driven&lt;/strong&gt;. People who will get things done even when no ticket has been assigned or clear ownership has been defined yet. That happens a lot.&lt;/p&gt;
&lt;p&gt;You need to bring ideas, not just execute someone else’s. We expect initiative, curiosity, and autonomy.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/957dd9f1-9623-477a-a943-16e35464be07_673x313.png&quot; alt=&quot;Screenshot of Mergify&apos;s vision and mission statement on their website&quot; /&gt;
&lt;em&gt;We provide a good summary of our vision and mission on our website.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Autonomy Isn’t a Buzzword, It’s the Filter&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Here’s a small story that stuck with me.&lt;/p&gt;
&lt;p&gt;We had a candidate once who asked me during the interview, “Who’s in charge of breaking down the project into tickets?”&lt;/p&gt;
&lt;p&gt;I said, “You are.”&lt;/p&gt;
&lt;p&gt;That was enough to scare them off — and that’s OK. At Mergify, you’ll be expected to do exactly that. Define your work, structure your plan, ask questions when you need to — but no one’s going to hand you a Jira board and a user story spec for every task.&lt;/p&gt;
&lt;p&gt;If that makes you nervous, we might not be the right place. If it makes you excited? &lt;a href=&quot;https://careers.mergify.com&quot;&gt;You should talk to us.&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Startups Are Messy. That’s the Point.&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;You can’t build a startup with neat little boxes around every role.&lt;/p&gt;
&lt;p&gt;One week, you might be writing code. The next, you’re giving a talk at a meetup. The week after, you’re helping a customer figure out something weird in their CI pipeline.&lt;/p&gt;
&lt;p&gt;We look for people who can jump between lanes without crashing the car. If you need clear job boundaries, startup life will drive you insane. But if you like wearing multiple hats (sometimes in one day), you’ll thrive here.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/e5ab8274-c078-4466-aa8b-082e9aae55eb_1376x864.webp&quot; alt=&quot;Illustration of wearing multiple hats at a startup&quot; /&gt;
&lt;em&gt;Wearing multiple hats.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Hiring Mistakes: Yep, We Made Some&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;One of our early missteps? Underestimating how hard remote work can be — for some people.&lt;/p&gt;
&lt;p&gt;We’re fully remote, and we love it. But not everyone is cut out for it. We’ve had brilliant engineers who struggled because they needed more structure, more hand-holding, and more real-time sync. (I wrote about this in &lt;a href=&quot;https://julien.danjou.info/blog/remote-work-great-but-not-perfect&quot;&gt;Remote Work: Great, But Not Perfect&lt;/a&gt;.) And we’ve learned the hard way that great resumes don’t always mean great remote workers.&lt;/p&gt;
&lt;p&gt;Now, we screen harder for that. Autonomy, again, is a big part of the puzzle.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Our Hiring Process (And Why We Meet in Person)&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Our process is pretty standard on the surface:&lt;/p&gt;
&lt;p&gt;👉 A technical test&lt;/p&gt;
&lt;p&gt;👉 A technical interview&lt;/p&gt;
&lt;p&gt;👉 A CEO chat&lt;/p&gt;
&lt;p&gt;👉 An onsite interview&lt;/p&gt;
&lt;p&gt;👉 Reference checks&lt;/p&gt;
&lt;p&gt;We’ve documented the whole thing &lt;a href=&quot;https://careers.mergify.com/hiring-process&quot;&gt;on our website&lt;/a&gt;, so there are no surprises.&lt;/p&gt;
&lt;p&gt;But one thing we insist on: &lt;strong&gt;we meet you in person&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Remote or not, hiring is a human process. A real-life conversation can reveal things that a dozen Zoom calls won’t. We’ve avoided a few bad hires because we took the time to meet face-to-face.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Motivation Over Résumé&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The biggest signal we look for? &lt;strong&gt;Why you want to join.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We try not to overexplain this part publicly, because it’s one of our most effective filters. But let’s just say: if your reason for leaving your current job is “looking for a remote job,” we’ll probably pass.&lt;/p&gt;
&lt;p&gt;We want people who are actively choosing this kind of work, people who are ready for the ambiguity, the responsibility, and, yes, the chaos.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Hiring for Roles You Don’t Know? Brutal.&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;When we hired our first designer, it took us &lt;em&gt;way&lt;/em&gt; longer than it should have. Why? Because we didn’t know how to assess the role.&lt;/p&gt;
&lt;p&gt;If you’ve never done the job yourself, hiring for it is like ordering dinner in a language you don’t speak. You might get lucky, but you probably won’t.&lt;/p&gt;
&lt;p&gt;We’ve learned to spend more time understanding what we actually need before we go looking for someone to do it. Sounds obvious. It’s not.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;What’s Hard Now?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Right now, our biggest challenge is finding candidates who combine technical skill with startup DNA.&lt;/p&gt;
&lt;p&gt;We’re looking for people who’ve spent a few years in early-stage companies, who know what it’s like to ship fast, wear multiple hats, and stay sane through ambiguity. Not just smart — adaptable.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;If That’s You…&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Then maybe we should talk. If you’re looking for more than just a job — if you want to build something, shape it, and take pride in it — you might belong here.&lt;/p&gt;
&lt;p&gt;We’re picky, yeah. It slows us down. But we’ve learned the cost of the wrong hire is much higher than waiting for the right one.&lt;/p&gt;
&lt;p&gt;So if you’re ready for the ride, &lt;a href=&quot;https://careers.mergify.com&quot;&gt;we’re hiring&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>When Great Tech Isn’t Enough</title><link>https://julien.danjou.info/blog/when-great-tech-isnt-enough/</link><guid isPermaLink="true">https://julien.danjou.info/blog/when-great-tech-isnt-enough/</guid><description>The Product That Never Shipped</description><pubDate>Wed, 26 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Welcome to the third post in our “we-built-something-and-killed-it” series.&lt;/p&gt;
&lt;p&gt;In the &lt;a href=&quot;https://julien.danjou.info/p/the-100000-mistake&quot;&gt;first chapter&lt;/a&gt;, we shared how we started building CI Optimizer—our ambitious attempt to help teams cut down on CI/CD costs.&lt;/p&gt;
&lt;p&gt;In the &lt;a href=&quot;https://julien.danjou.info/p/when-nobody-wants-your-product&quot;&gt;second&lt;/a&gt;, we explained why that effort never made it past the runway: while the problem existed, no one really wanted the solution.&lt;/p&gt;
&lt;p&gt;But that wasn’t the end of the story.&lt;/p&gt;
&lt;p&gt;Because just as we were winding down CI Optimizer, something else started to take shape—almost accidentally.&lt;/p&gt;
&lt;h2&gt;From CI Cost to CI Chaos&lt;/h2&gt;
&lt;p&gt;As we were working on CI Optimizer, we had to dig deeper into CI platforms like GitHub Actions or CircleCI. We needed to understand the structure, failures, and performance of CI pipelines to measure their cost.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/59ac52ab-14c5-4f1a-8f0c-686e3d94bc06_1376x864.webp&quot; alt=&quot;Illustration of digging into CI pipeline failures and reliability issues&quot; /&gt;&lt;/p&gt;
&lt;p&gt;And the more we explored, the more something else stood out: teams weren’t just struggling with &lt;strong&gt;CI/CD costs&lt;/strong&gt;—they were struggling with &lt;strong&gt;CI reliability&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;❗Flaky tests.&lt;/p&gt;
&lt;p&gt;❗Unreliable runners.&lt;/p&gt;
&lt;p&gt;❗Timeouts.&lt;/p&gt;
&lt;p&gt;❗Random infra failures.&lt;/p&gt;
&lt;p&gt;And as users of our Merge Queue product kept telling us:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“Our workflow is fine—until CI starts acting up.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So we asked ourselves a new question:&lt;/p&gt;
&lt;p&gt;💡 What if we stopped focusing on &lt;strong&gt;how much CI costs&lt;/strong&gt;, and started looking at &lt;strong&gt;how much CI hurts&lt;/strong&gt;?&lt;/p&gt;
&lt;p&gt;That’s how the idea for our next product—&lt;strong&gt;CI Issues&lt;/strong&gt;—was born.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The Pivot: CI Issues&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;CI Issues was meant to do one thing really well:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Track, identify, and alert on CI problems before they silently torpedo developer productivity.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We wanted to give teams &lt;strong&gt;insight and visibility&lt;/strong&gt; into:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;How often their tests flaked&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Whether their CI infrastructure was unreliable&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Which PRs were impacted&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Which workflows deserved attention&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The goal wasn’t just dashboards. It was &lt;strong&gt;detection and action&lt;/strong&gt;. You’d be able to see patterns, set alerts, and flag recurring issues before developers noticed them.&lt;/p&gt;
&lt;p&gt;And as we started to pitch the concept to engineers, the excitement was real:&lt;/p&gt;
&lt;p&gt;💬 “We have this exact pain.”&lt;/p&gt;
&lt;p&gt;💬 “We’ve built half of this internally.”&lt;/p&gt;
&lt;p&gt;💬 “Please let us know when it’s ready.”&lt;/p&gt;
&lt;p&gt;We felt like we were onto something.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The R&amp;amp;D Rabbit Hole&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;So we jumped in headfirst. We already had code collecting and analyzing CI data, so we started adapting it for CI Issues.&lt;/p&gt;
&lt;p&gt;We ran the system internally, refined metrics, tested detection logic, built a first UI. And then we iterated. And iterated. And iterated again.&lt;/p&gt;
&lt;p&gt;But something was off.&lt;/p&gt;
&lt;p&gt;Every time we looked at what we had, the same thought came back:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“This is good… but it’s not a product.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It was barely working for us internally. Even we had trouble using it.&lt;/p&gt;
&lt;p&gt;It was noisy. It was complex. It was fragile. It wasn’t obvious how to deploy or operate it at scale.&lt;/p&gt;
&lt;p&gt;We had built &lt;strong&gt;tech&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;But we hadn’t designed a &lt;strong&gt;product&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/65407c22-58d2-4faf-a612-5fa983ebabfd_1376x864.webp&quot; alt=&quot;Illustration of building tech without product design&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The Realization That Stopped Us&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;After almost a year of work, we paused and took a step back. And it hit us:&lt;/p&gt;
&lt;p&gt;We had made the &lt;strong&gt;same mistake&lt;/strong&gt; again—but in a different way.&lt;/p&gt;
&lt;p&gt;With CI Optimizer, we had &lt;strong&gt;no market&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;With CI Issues, we had &lt;strong&gt;no design&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This time, it wasn’t the problem that was flawed—it was our approach.&lt;/p&gt;
&lt;p&gt;We had focused on research, experimentation, pipelines, metrics, code—but we hadn’t put the same energy into figuring out &lt;strong&gt;how the product should be used&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;How would teams onboard?&lt;/p&gt;
&lt;p&gt;How would they configure it?&lt;/p&gt;
&lt;p&gt;How would they act on the data?&lt;/p&gt;
&lt;p&gt;What does success look like for them?&lt;/p&gt;
&lt;p&gt;The longer we waited to answer those questions, the more we realized:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;💣 “If we ship this now, we’ll be building another tool that’s hard to use, hard to maintain, and ultimately, unadopted.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So we made the call—again.&lt;/p&gt;
&lt;p&gt;We stopped.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;What We Learned (This Time)&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;This second failure didn’t sting the same way as the first.&lt;/p&gt;
&lt;p&gt;In fact, it felt like a necessary part of the journey.&lt;/p&gt;
&lt;p&gt;Here’s what we learned:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Validation isn’t enough—you need design.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Even if users want a solution, they won’t use a product that’s hard to operate or understand.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Great tech doesn’t mean great UX.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;CI Issues worked, technically—but without thoughtful design, it was dead in the water.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;You need both clarity and empathy.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Clarity on what you’re solving, and empathy for how your users will experience it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;strong&gt;What’s Next?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The story doesn’t end here.&lt;/p&gt;
&lt;p&gt;CI Issues gave us a powerful insight into how fragile and painful the CI experience can be—and how underserved engineers still are when things go wrong.&lt;/p&gt;
&lt;p&gt;So we took everything we learned from CI Optimizer and CI Issues, and went back to the drawing board—with a new vision, new design principles, and a better understanding of how to build the right thing the right way.&lt;/p&gt;
&lt;p&gt;Stay tuned for the final post in the series: &lt;strong&gt;what we built next, and how it’s going to change how developers deal with CI failures.&lt;/strong&gt;&lt;/p&gt;
</content:encoded></item><item><title>“It’s Complicated” Is Not an Excuse</title><link>https://julien.danjou.info/blog/its-complicated-is-not-an-excuse/</link><guid isPermaLink="true">https://julien.danjou.info/blog/its-complicated-is-not-an-excuse/</guid><description>“It’s Complicated” Is Not an Excuse</description><pubDate>Tue, 11 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I spend a lot of time talking to engineers.&lt;/p&gt;
&lt;p&gt;I ask them about &lt;strong&gt;design choices&lt;/strong&gt;, &lt;strong&gt;technical decisions&lt;/strong&gt;, and &lt;strong&gt;why something is built a certain way&lt;/strong&gt;. I try to understand &lt;strong&gt;why this feature is so cumbersome to use&lt;/strong&gt;, &lt;strong&gt;why this API is so convoluted&lt;/strong&gt;, or &lt;strong&gt;why the user experience feels unnecessarily difficult&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;And more often than not, the response I get is:&lt;/p&gt;
&lt;p&gt;💬 &lt;strong&gt;“Well… it’s complicated.”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sure. Everything is complicated.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That’s why you’re here. That’s why you’re an &lt;strong&gt;engineer&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;But &lt;strong&gt;“it’s complicated” should never be an excuse for bad design.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/51d36525-99b3-4bc2-bfc6-71543082d6b4_1376x864.png&quot; alt=&quot;Illustration of engineers using complexity as an excuse for bad design&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Imagine If Other Professions Worked Like This&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Let’s take &lt;strong&gt;a bakery&lt;/strong&gt;, for example.&lt;/p&gt;
&lt;p&gt;You walk in and ask for &lt;strong&gt;a loaf of bread&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The baker hands you a cup of flour and some water.&lt;/p&gt;
&lt;p&gt;🫤 &lt;strong&gt;“Uhh… I was expecting actual bread.”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;💬 &lt;strong&gt;“Well… it’s complicated.”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;💬 &lt;strong&gt;“We’d have to mix the dough, let it rise, bake it for a while…”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;💬 &lt;strong&gt;“That’s a lot of steps, so we just decided to give you the raw ingredients instead.”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This is exactly how software feels someday.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When users interact with your product, they don’t want to assemble the damn bread. They just want &lt;strong&gt;something that works&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Your job as an engineer is to handle complexity—not push it onto the user.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The Difference Between Good and Bad Engineering&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Look, I get it. Engineering &lt;strong&gt;is&lt;/strong&gt; hard.&lt;/p&gt;
&lt;p&gt;Making things simple &lt;strong&gt;is&lt;/strong&gt; difficult.&lt;/p&gt;
&lt;p&gt;Abstracting complexity &lt;strong&gt;takes effort&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;But &lt;strong&gt;great engineers&lt;/strong&gt; don’t just write code—they design experiences.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;bad engineer&lt;/strong&gt; builds something difficult and says, &lt;strong&gt;“Well, it’s complicated.”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;great engineer&lt;/strong&gt; builds something difficult and makes it look &lt;strong&gt;simple.&lt;/strong&gt; (More on what makes &lt;a href=&quot;https://julien.danjou.info/blog/how-to-be-a-great-software-engineer&quot;&gt;a great software engineer&lt;/a&gt;.)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;🔹 &lt;strong&gt;Bad engineering forces users to deal with complexity.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;🔹 &lt;strong&gt;Good engineering hides the complexity behind smart design.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Take Apple, for example. You know what’s &lt;strong&gt;actually complicated&lt;/strong&gt;?&lt;/p&gt;
&lt;p&gt;🔹 Compressing a 4K video into a tiny file.&lt;/p&gt;
&lt;p&gt;🔹 Rendering realistic lighting effects in real-time on an iPhone.&lt;/p&gt;
&lt;p&gt;🔹 Syncing all your messages, contacts, and photos seamlessly across devices.&lt;/p&gt;
&lt;p&gt;But do Apple users &lt;strong&gt;ever have to think about any of that?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;No. It &lt;strong&gt;just works&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;That’s &lt;strong&gt;good engineering.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/dad2f44b-6a86-4552-b29c-d08dce3d0ea3_1376x864.png&quot; alt=&quot;Illustration of good engineering making complex things feel simple&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Stop Saying “It’s Complicated”—Start Making It Simple&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;When you hear yourself saying, &lt;strong&gt;“It’s complicated”&lt;/strong&gt;, stop for a second and think:&lt;/p&gt;
&lt;p&gt;🛑 &lt;strong&gt;Are you solving a hard problem in the simplest way possible?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;🛑 &lt;strong&gt;Or are you just passing the complexity to the user?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If it’s the latter, &lt;strong&gt;you haven’t finished the job yet.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Because real engineering isn’t about making things work.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It’s about making things work… simply.&lt;/strong&gt;&lt;/p&gt;
</content:encoded></item><item><title>When Nobody Wants Your Product</title><link>https://julien.danjou.info/blog/when-nobody-wants-your-product/</link><guid isPermaLink="true">https://julien.danjou.info/blog/when-nobody-wants-your-product/</guid><description>The Moment We Realized CI Optimizer Was Doomed</description><pubDate>Tue, 04 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In the first part of this series, we introduced &lt;em&gt;CI Optimizer&lt;/em&gt;, a product we were convinced would help engineering teams reduce their CI/CD costs. Given the economic downturn in 2023, we saw budgets tightening, companies folding, and engineering teams being forced to justify every dollar they spent.&lt;/p&gt;
&lt;p&gt;If you missed the first part, &lt;a href=&quot;https://julien.danjou.info/blog/the-100000-mistake&quot;&gt;read it here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It seemed like the perfect time to launch a tool that would bring cost visibility and optimization to CI/CD workflows.&lt;/p&gt;
&lt;p&gt;We started building immediately, setting up a landing page and a waitlist, and running early customer interviews. Our approach was clear:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Build the product.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Talk to potential users.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Iterate based on feedback.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;But as we reached out to customers, one thing became clear:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;💡 Nobody really cared about optimizing CI/CD costs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This was the moment we realized we were building something that might never find an audience.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/735a74df-429a-4df9-9aa5-9115ff00c989_1376x864.webp&quot; alt=&quot;Illustration of building a product nobody wants&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Trying to Sell the Product Before It Existed&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;I strongly believe that you should be selling a product before it even exists. If you can’t generate demand when it’s just an idea, chances are you won’t generate demand once it’s built.&lt;/p&gt;
&lt;p&gt;So, as we were writing the first lines of code, we also launched:&lt;/p&gt;
&lt;p&gt;✅ A &lt;strong&gt;marketing campaign&lt;/strong&gt; to build awareness.&lt;/p&gt;
&lt;p&gt;✅ A &lt;strong&gt;landing page&lt;/strong&gt; with a waitlist.&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;Customer outreach&lt;/strong&gt; to gauge interest.&lt;/p&gt;
&lt;p&gt;Our goal was to &lt;strong&gt;validate demand early&lt;/strong&gt;—before we wasted months building something nobody wanted.&lt;/p&gt;
&lt;p&gt;But things didn’t go as expected.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The First Red Flag: Engineers Didn’t Care&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;As we started &lt;strong&gt;talking to users&lt;/strong&gt;, the first warning sign was that &lt;strong&gt;engineers were simply not interested&lt;/strong&gt; in optimizing their CI/CD costs.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;💬 &lt;strong&gt;“Sure, spending less money is nice, but it’s not a priority.”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;💬 &lt;strong&gt;“We’ve never been asked to reduce our CI/CD spend.”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;💬 &lt;strong&gt;“CI is just a necessary cost of doing business.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This was surprising. We expected companies to be actively looking for &lt;strong&gt;ways to cut costs&lt;/strong&gt;, but instead, we found:&lt;/p&gt;
&lt;p&gt;👉 &lt;strong&gt;Engineers weren’t incentivized to optimize costs.&lt;/strong&gt; Most of them were measured by &lt;strong&gt;features delivered&lt;/strong&gt; and &lt;strong&gt;bugs fixed&lt;/strong&gt;, not by how much they spent on infrastructure.&lt;/p&gt;
&lt;p&gt;👉 &lt;strong&gt;Budgets were tight, but existing expenses weren’t scrutinized.&lt;/strong&gt; Many teams were cutting &lt;strong&gt;new&lt;/strong&gt; expenditures, but &lt;strong&gt;existing CI/CD costs were just accepted&lt;/strong&gt; as part of doing business.&lt;/p&gt;
&lt;p&gt;👉 &lt;strong&gt;It wasn’t an engineering problem—it was a finance problem.&lt;/strong&gt; Even when engineers acknowledged CI/CD spending was high, they said, &lt;strong&gt;“This isn’t my job to fix.”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In short:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;🚨 &lt;strong&gt;We had built a solution for a problem our audience didn’t care about.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But we weren’t ready to give up yet.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The Second Red Flag: Talking to the Wrong People&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Since engineering teams didn’t seem to care, we were often &lt;strong&gt;redirected to FinOps teams&lt;/strong&gt;—the financial teams responsible for tracking cloud spend.&lt;/p&gt;
&lt;p&gt;So we thought, &lt;strong&gt;“Great! Maybe this is our actual target audience.”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We started talking to FinOps teams, and here’s what we discovered:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;💬 &lt;strong&gt;“We don’t need another tool—we just need a report in a spreadsheet.”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;💬 &lt;strong&gt;“Can you just give us an API so we can generate cost breakdowns?”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;💬 &lt;strong&gt;“We don’t want to ‘optimize’ CI/CD automatically. We just need visibility.”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;💬 &lt;strong&gt;“If we were to buy your product, we’d need more than reporting. We want automatic cost optimization.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here’s where we ran into &lt;strong&gt;our second major issue&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;🚨 &lt;strong&gt;We were not equipped to build a product for FinOps teams.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We understood &lt;strong&gt;engineers&lt;/strong&gt;. We had deep experience with &lt;strong&gt;CI/CD workflows&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;But we knew &lt;strong&gt;nothing&lt;/strong&gt; about selling to FinOps.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Selling to FinOps teams is a completely different game.&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;They care about &lt;strong&gt;budgets, forecasting, and high-level cost reporting&lt;/strong&gt;, not about &lt;strong&gt;how CI/CD actually works.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Even worse:&lt;/p&gt;
&lt;p&gt;❌ &lt;strong&gt;The product we had in mind was too technical for FinOps teams.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;❌ &lt;strong&gt;The version they needed was much more complex and would take a year to build.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;❌ &lt;strong&gt;We would be competing against massive cloud cost monitoring tools, not other DevOps tools.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;At this point, we had two choices:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Keep building a product for engineers who didn’t care.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Completely pivot to a new audience we didn’t understand.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Neither option looked good.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The Hard Decision: Killing the Product&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;By the six-month mark, we had spent:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;🕚 &lt;strong&gt;Hundreds of hours&lt;/strong&gt; building a proof-of-concept.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;📞 &lt;strong&gt;Countless customer calls&lt;/strong&gt; trying to validate the idea.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;💬 &lt;strong&gt;Weeks refining our messaging&lt;/strong&gt; to see if we could spark interest.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But deep down, we knew the truth:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;❌ &lt;strong&gt;Engineers wouldn’t pay for cost optimization.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;❌ &lt;strong&gt;FinOps teams needed something completely different.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;❌ &lt;strong&gt;There was no clear path forward.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And so, after &lt;strong&gt;six months of work&lt;/strong&gt;, we made the hardest decision a product team can make:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We killed the project.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/69edf6e0-23d2-4c40-aebd-25e4463167ca_1376x864.png&quot; alt=&quot;Illustration of killing a product that has no market fit&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Instead of pushing forward with a product that had no market, we &lt;strong&gt;pivoted to something else&lt;/strong&gt;—which I’ll reveal in the final part of this series.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Lessons Learned&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Even though we ultimately abandoned &lt;em&gt;CI Optimizer&lt;/em&gt;, the experience taught us some &lt;strong&gt;critical lessons&lt;/strong&gt; about building new products:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Talk to Customers Before Writing Code&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We should have validated demand &lt;strong&gt;before&lt;/strong&gt; starting development. Building first and testing later is a &lt;strong&gt;risky&lt;/strong&gt; approach. Fortunately we mitigated this by talking while building.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Engineers Don’t Always Care About Cost Savings&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Developers are focused on &lt;strong&gt;shipping code&lt;/strong&gt;, not &lt;strong&gt;cutting costs&lt;/strong&gt;. If a product &lt;strong&gt;doesn’t directly impact their work&lt;/strong&gt;, they won’t engage with it.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Just Because a Problem Exists Doesn’t Mean It Needs a Product&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Companies do spend too much on CI/CD, but that doesn’t mean they’re looking for a tool to fix it. Some problems are simply &lt;strong&gt;not painful enough&lt;/strong&gt; to justify a new product.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Selling to Finance Teams is a Whole Different Game&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;FinOps teams think differently from engineers. If your product doesn’t fit into their &lt;strong&gt;existing finance workflows&lt;/strong&gt;, they won’t use it.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Know When to Walk Away&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;One of the hardest skills in startups is knowing &lt;strong&gt;when to cut your losses&lt;/strong&gt;. We could have wasted another 6–12 months building something nobody wanted. Instead, we chose to &lt;strong&gt;fail fast and pivot.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;What’s Next?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Even though &lt;strong&gt;CI Optimizer never launched&lt;/strong&gt;, it wasn’t a wasted effort.&lt;/p&gt;
&lt;p&gt;In fact, the insights we gained from this failure led us to build &lt;strong&gt;something even better.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In &lt;strong&gt;Part 3&lt;/strong&gt;, I’ll reveal how we took everything we learned from this failure and &lt;strong&gt;pivoted to a product that actually resonated with engineers&lt;/strong&gt;—and how that decision changed the trajectory of Mergify.&lt;/p&gt;
</content:encoded></item><item><title>The Hidden Cost of Badly Typed Python Wrappers</title><link>https://julien.danjou.info/blog/the-hidden-cost-of-badly-typed-python/</link><guid isPermaLink="true">https://julien.danjou.info/blog/the-hidden-cost-of-badly-typed-python/</guid><description>And How to Fix Them</description><pubDate>Tue, 25 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;What about some technical stuff this week?&lt;/p&gt;
&lt;p&gt;Writing wrappers in Python is a common practice. Whether it’s to simplify function calls, encapsulate complexity, or create a cleaner API, wrapping functions can be a great way to organize code. But there’s a catch: &lt;strong&gt;if you’re not typing your wrappers correctly, you might be introducing subtle bugs that your type checker won’t catch.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you’re using &lt;strong&gt;&lt;a href=&quot;https://mypy-lang.org/&quot;&gt;Mypy&lt;/a&gt;&lt;/strong&gt; (or another static type checker like &lt;a href=&quot;https://julien.danjou.info/blog/the-journey-of-embracing-linters&quot;&gt;ruff&lt;/a&gt;), you should be careful about &lt;strong&gt;blindly passing&lt;/strong&gt; &lt;code&gt;*args&lt;/code&gt; &lt;strong&gt;and&lt;/strong&gt; &lt;code&gt;**kwargs&lt;/code&gt; &lt;strong&gt;as&lt;/strong&gt; &lt;code&gt;Any&lt;/code&gt;—because doing so effectively turns off your type checker, making your code vulnerable to runtime errors that should have been caught statically.&lt;/p&gt;
&lt;p&gt;Let’s dive into &lt;strong&gt;why this is a problem, why traditional approaches fail, and what the correct way to handle wrapped functions is.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/d2a0693b-8828-4e0f-955d-0fdf30dd363e_1376x864.png&quot; alt=&quot;Illustration of type checking pitfalls in Python wrapper functions&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;The Common but Flawed Wrapper Pattern&lt;/h2&gt;
&lt;p&gt;Here’s a classic example of an incorrectly typed wrapper function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import typing

def make_request(url: str, *args: typing.Any, **kwargs: typing.Any):
    return send_request(HttpClient(url), *args, **kwargs)

def send_request(client: &quot;HttpClient&quot;, method: str = &quot;GET&quot;, timeout: int = 5) -&amp;gt; str:
    return f&quot;Request sent to {client.url} with method {method} and timeout {timeout}s&quot;

class HttpClient:
    def __init__(self, url: str):
        self.url = url
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What’s the issue here?&lt;/p&gt;
&lt;p&gt;At first glance, this seems fine. We’re creating an &lt;code&gt;HttpClient&lt;/code&gt; for a given url and passing all additional arguments directly to send_request().&lt;/p&gt;
&lt;p&gt;But the problem arises when you pass the wrong arguments:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;make_request(&quot;https://example.com&quot;, method=&quot;POST&quot;, timout=10)  # ❌ Typo in &quot;timeout&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will result in a &lt;strong&gt;runtime error&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TypeError: send_request() got an unexpected keyword argument &apos;timout&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since &lt;code&gt;make_request()&lt;/code&gt; uses &lt;code&gt;*args: Any&lt;/code&gt; and &lt;code&gt;**kwargs: Any&lt;/code&gt;, &lt;strong&gt;Mypy won’t flag this mistake.&lt;/strong&gt; The type checker has no way to verify whether the arguments passed to &lt;code&gt;make_request()&lt;/code&gt; are valid for &lt;code&gt;send_request()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Using&lt;/strong&gt; &lt;code&gt;Any&lt;/code&gt; &lt;strong&gt;like this completely disables type checking, making Mypy useless for catching argument mismatches.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/485a66d5-412f-479a-b2d5-c4607c1ee06e_1376x864.webp&quot; alt=&quot;Illustration of using Any disabling Mypy type checking in Python&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;What About Using ParamSpec? (And Why It Doesn’t Work)&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;A natural instinct is to use &lt;code&gt;ParamSpec&lt;/code&gt; to tell Mypy that &lt;code&gt;make_request()&lt;/code&gt; should take the exact same arguments as send_request().&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from typing import ParamSpec, Callable

P = ParamSpec(&quot;P&quot;)

def make_request(url: str, *args: P.args, **kwargs: P.kwargs):
    return send_request(HttpClient(url), *args, **kwargs)  # ❌ Won&apos;t work
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why doesn’t this work?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ParamSpec&lt;/code&gt; is &lt;strong&gt;only useful for decorators and higher-order functions&lt;/strong&gt;—functions that return another function.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It &lt;strong&gt;does not work for simple wrappers&lt;/strong&gt; like this, where you’re directly calling the function inside the wrapper.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you try this, &lt;strong&gt;Mypy will complain&lt;/strong&gt; that ParamSpec is being used incorrectly.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;This means that traditional wrapper functions in Python&lt;/strong&gt;—where you take &lt;code&gt;*args&lt;/code&gt; and &lt;code&gt;**kwargs&lt;/code&gt; and pass them blindly—&lt;strong&gt;are no longer a good practice in a world where static typing matters.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The Correct Approach: Using&lt;/strong&gt; &lt;code&gt;functools.partial&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Instead of directly calling send_request() within make_request(), we should &lt;strong&gt;return a callable function using&lt;/strong&gt; &lt;code&gt;functools.partial&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here’s how you do it properly:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from functools import partial

def make_request(url: str):
    return partial(send_request, HttpClient(url))

# Correct Usage
request = make_request(&quot;https://example.com&quot;)
print(request(method=&quot;POST&quot;, timeout=10))  # ✅ Works correctly

# Incorrect Usage
print(request(method=&quot;POST&quot;, timout=10))  # ❌ Mypy will catch this!
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;strong&gt;Why This Works&lt;/strong&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;✅ &lt;strong&gt;Mypy can now properly check argument correctness:&lt;/strong&gt; request has the exact same signature as &lt;code&gt;send_request()&lt;/code&gt;, ensuring proper type safety.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;✅ &lt;strong&gt;No more unexpected runtime errors:&lt;/strong&gt; if you pass an invalid argument, &lt;strong&gt;Mypy will flag it before you even run the code.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;✅ &lt;strong&gt;More maintainable code:&lt;/strong&gt; this pattern makes it clear &lt;strong&gt;what arguments belong to what function&lt;/strong&gt; instead of having them blindly passed along.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;strong&gt;Key Takeaways&lt;/strong&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Stop Using&lt;/strong&gt; &lt;code&gt;*args: Any, **kwargs: Any&lt;/code&gt; &lt;strong&gt;in Wrappers:&lt;/strong&gt; this disables type checking and opens your code to hard-to-debug errors.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ParamSpec is NOT a fix:&lt;/strong&gt; it only works for decorators and cannot be used to type generic wrapper functions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use&lt;/strong&gt; &lt;code&gt;functools.partial&lt;/code&gt; &lt;strong&gt;Instead&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This ensures that type checkers can properly verify arguments &lt;strong&gt;while keeping the flexibility of a wrapper.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Python’s type system has evolved significantly, and many old habits—like blindly wrapping functions with Any—should now considered bad practice.&lt;/p&gt;
&lt;p&gt;By using &lt;code&gt;functools.partial&lt;/code&gt;, you ensure that your wrapped functions remain type-safe, predictable, and error-free.&lt;/p&gt;
&lt;p&gt;Start refactoring your wrappers today—you’ll have fewer bugs, cleaner code, and a much happier type checker.&lt;/p&gt;
&lt;p&gt;Have you encountered issues with typing wrappers in Python? Do you have alternative approaches? Let’s discuss in the comments! 🚀&lt;/p&gt;
</content:encoded></item><item><title>The $100,000 Mistake</title><link>https://julien.danjou.info/blog/the-100000-mistake/</link><guid isPermaLink="true">https://julien.danjou.info/blog/the-100000-mistake/</guid><description>How We Spent 6 Months Building a CI Tool Nobody Asked For</description><pubDate>Tue, 18 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;&lt;strong&gt;A Journey into Startup Reality&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Not every startup success story begins with a garage, two co-founders, and an overnight explosion of users. And not every failure is a dramatic, fiery crash. Some of the most valuable lessons happen in the quieter moments—when you’ve built something, spent months refining it, and then realized you were &lt;strong&gt;solving the wrong problem&lt;/strong&gt; all along.&lt;/p&gt;
&lt;p&gt;This is the story of &lt;strong&gt;CI Optimizer&lt;/strong&gt;, a product we believed would transform the way companies managed their CI/CD costs. We spent &lt;strong&gt;six months&lt;/strong&gt; designing, building, and testing it—only to ultimately &lt;strong&gt;kill it before launch&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Why? Because we made &lt;strong&gt;one fundamental mistake&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This three-part series is not just about the technical challenges of optimizing CI/CD or the intricacies of pricing cloud infrastructure. It’s about &lt;strong&gt;the hard reality of building a product, talking to customers, and realizing you missed the mark.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you’re an entrepreneur, a product builder, or just someone fascinated by the messy, unpredictable world of startups, this series is for you.&lt;/p&gt;
&lt;p&gt;Let’s start at the beginning.&lt;/p&gt;
&lt;h2&gt;How It Started&lt;/h2&gt;
&lt;p&gt;At the end of 2022, &lt;strong&gt;Mergify was on a roll.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We had spent the past year growing steadily, tripling our revenue, refining our &lt;strong&gt;&lt;a href=&quot;https://mergify.com/product/merge-queue&quot;&gt;Merge Queue&lt;/a&gt;&lt;/strong&gt; &lt;a href=&quot;https://mergify.com/product/merge-queue&quot;&gt;product&lt;/a&gt;, and deepening our place in the DevOps ecosystem. Our customers were engaged, our product-market fit felt strong, and our team fired on all cylinders.&lt;/p&gt;
&lt;p&gt;But as the year drew to a close, something in the air felt different.&lt;/p&gt;
&lt;p&gt;Conversations with prospects were shifting. Instead of discussing new features and scaling up their usage, they were hesitant. Startups—our core audience—were tightening their budgets. Investors were slowing down. &lt;strong&gt;Some of our customers simply disappeared.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;First, it was the crypto companies. Then, real estate tech. One by one, they went silent—not because they didn’t love our product, but because their businesses were collapsing. &lt;a href=&quot;https://en.wikipedia.org/wiki/2022_stock_market_decline&quot;&gt;The market was crashing&lt;/a&gt;. &lt;a href=&quot;https://news.crunchbase.com/venture/north-american-startup-funding-q4-2022/&quot;&gt;Funding was drying up.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/56767aca-7503-4d8a-921e-183a8e0621f4_1037x871.webp&quot; alt=&quot;Chart showing the 2022 startup market crash and funding decline&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The message became clear:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“We love Merge Queue, but our budget is frozen.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We knew we couldn’t just sit back and hope the market would bounce back. We needed to adapt. That’s how startups survive.&lt;/p&gt;
&lt;p&gt;And that’s when we had what we thought was a &lt;strong&gt;brilliant idea&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The Birth of CI Optimizer&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;One undeniable truth about software engineering is that &lt;strong&gt;CI/CD is expensive&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Every build, every test, every deployment—it all costs money.&lt;/p&gt;
&lt;p&gt;At scale, those costs grow exponentially, often without teams fully understanding where their budget is going. Developers push a change, run a full test suite, and move on. But in the background, &lt;strong&gt;cloud bills are racking up&lt;/strong&gt;, and finance teams are left wondering where all that money is going.&lt;/p&gt;
&lt;p&gt;So we thought:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“What if we built a tool that gave teams complete visibility into their CI/CD costs? What if we could identify waste, eliminate unnecessary builds, and optimize pipelines automatically?” What if we could help companies save money on their CI without slowing them down?”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It sounded like a no-brainer. &lt;strong&gt;We’d build a smart system that could analyze CI/CD usage and recommend cost-saving adjustments—maybe even automate them.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is something we could even use ourselves to save on our CI/CD bills.&lt;/p&gt;
&lt;p&gt;This wasn’t just an idea—we were convinced we had struck gold.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/c6bf28a5-95f8-420f-ab2b-477fee75984d_1376x864.webp&quot; alt=&quot;Illustration of building a CI/CD cost optimization tool&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Building the Future of CI/CD Cost Optimization&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;By early 2023, we had begun prototyping.&lt;/p&gt;
&lt;p&gt;The first step? Connecting to GitHub Actions. GitHub, like many CI/CD providers, is well known for not providing a good report on cost analysis (still true as of today). Since GitHub is where all of our customers are, this made sense.&lt;/p&gt;
&lt;p&gt;We needed to pull in detailed CI/CD usage data and break down the cost per minute of every build. Our system would scan pipelines, report metrics, identify inefficiencies, and provide actionable insights—like which jobs were wasting the most money.&lt;/p&gt;
&lt;p&gt;It felt like a natural extension of what I had worked on years earlier at Datadog, where I had pushed for replacing CPU seconds with dollar values in profiling tools. The goal was simple: &lt;strong&gt;make CI/CD costs tangible, trackable, and optimizable.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We saw an opportunity to build something that would fit neatly into engineering workflows. The logic was airtight:&lt;/p&gt;
&lt;p&gt;✅ Developers hate waiting for builds.&lt;/p&gt;
&lt;p&gt;✅ Developers need to get more budget.&lt;/p&gt;
&lt;p&gt;✅ We could solve both problems at once.&lt;/p&gt;
&lt;p&gt;Or so we thought.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;What We Hoped to Prove&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Our &lt;strong&gt;plan for early 2023&lt;/strong&gt; was straightforward:&lt;/p&gt;
&lt;p&gt;1️⃣ Build an MVP that could &lt;strong&gt;accurately measure CI/CD costs&lt;/strong&gt; at a granular level.&lt;/p&gt;
&lt;p&gt;2️⃣ Talk to &lt;strong&gt;real users&lt;/strong&gt; and validate whether CI/CD engineers cared about cost optimization.&lt;/p&gt;
&lt;p&gt;3️⃣ Launch a &lt;strong&gt;first version of CI Optimizer&lt;/strong&gt; and start onboarding teams.&lt;/p&gt;
&lt;p&gt;We expected engineers to tell us:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“This is amazing! We’ve been waiting for something like this!”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But that’s not what happened.&lt;/p&gt;
&lt;p&gt;Instead, we hit an unexpected roadblock that completely changed the course of the project. (Read what happened next in &lt;a href=&quot;https://julien.danjou.info/blog/when-nobody-wants-your-product&quot;&gt;When Nobody Wants Your Product&lt;/a&gt;.)&lt;/p&gt;
</content:encoded></item><item><title>SaaS Pricing is Hard</title><link>https://julien.danjou.info/blog/saas-pricing-is-hard/</link><guid isPermaLink="true">https://julien.danjou.info/blog/saas-pricing-is-hard/</guid><description>Our Journey at Mergify</description><pubDate>Tue, 04 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Pricing is one of the hardest things to get right in SaaS. If you’re a startup founder, especially in B2B, you’ve likely wrestled with pricing questions:&lt;/p&gt;
&lt;p&gt;💰 &lt;strong&gt;How much should I charge?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;📊 &lt;strong&gt;What pricing model makes sense?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;⚖️ &lt;strong&gt;How do I ensure fairness while maximizing revenue?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;At &lt;a href=&quot;https://mergify.com&quot;&gt;Mergify&lt;/a&gt;, we’ve spent years experimenting, iterating, and learning the hard way. Here’s a breakdown of our journey—and what we’d do differently if we had to start over.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;How We Picked Our First Pricing Model&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;When we first launched Mergify, we had no idea what the right pricing model should be. So, like many startups, we &lt;strong&gt;copied GitHub&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;We charged &lt;strong&gt;per user, based on the size of the entire organization.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This meant that if a company had 200 engineers, they had to pay for all 200 engineers—even if only 20 or 30 of them actually used Mergify.&lt;/p&gt;
&lt;p&gt;For small companies (e.g., 20–30 engineers), this wasn’t a big deal. They usually had one team using Mergify across all their repos. But as we grew and larger companies came in, things got tricky.&lt;/p&gt;
&lt;p&gt;🛑 &lt;strong&gt;Larger companies had multiple teams, and only some teams used Mergify.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;💰 &lt;strong&gt;They didn’t want to pay for everyone—just for the engineers who actually needed it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We needed to change.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/52ea4806-fcf2-43cc-b5e2-cd505bc8d0f2_1376x864.webp&quot; alt=&quot;Illustration of SaaS pricing evolution from per-organization to per-collaborator model&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;Counting Users the “Right” Way&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;To address this, we moved to a new model:&lt;/p&gt;
&lt;p&gt;✅ Instead of charging per &lt;strong&gt;organization&lt;/strong&gt;, we charged per &lt;strong&gt;collaborator&lt;/strong&gt;—engineers who had &lt;strong&gt;write access&lt;/strong&gt; to a repository where Mergify was active.&lt;/p&gt;
&lt;p&gt;This felt fairer. A company with 100 engineers could now pay only for the repositories where Mergify was used, rather than for the entire org.&lt;/p&gt;
&lt;p&gt;At the same time, we &lt;strong&gt;doubled our price per user&lt;/strong&gt;. Why?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Customers were already seeing the value, and price wasn’t their biggest concern.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The new model lowered the user count for most companies, so we had to balance revenue.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;&lt;strong&gt;The Math: Why This Worked&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;For a company with &lt;strong&gt;100 engineers&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Old model:&lt;/strong&gt; $2 per user × 100 users = $200/month&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;New model:&lt;/strong&gt; $4 per user × 50 repo contributors = $200/month&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For many customers, their bill stayed roughly the same. But they were &lt;strong&gt;happier&lt;/strong&gt; because they felt they were paying for what they actually used.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;Fairness is Everything&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;But the journey didn’t stop there.&lt;/p&gt;
&lt;p&gt;Larger organizations often &lt;strong&gt;gave write access to all engineers by default&lt;/strong&gt;, even if only a subset was actually making commits. That meant some companies were being charged for engineers who weren’t actively contributing.&lt;/p&gt;
&lt;p&gt;So we introduced another iteration:&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;Charging per “active user”—engineers who actually made commits.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This approach, inspired by &lt;strong&gt;&lt;a href=&quot;https://slack.com/help/articles/23546798305171-FAQ--Updates-to-Slack%E2%80%99s-active-user-calculation&quot;&gt;Slack’s active user model&lt;/a&gt;&lt;/strong&gt;, made more sense. Now, companies only paid for users who actively used Mergify.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/8295f69d-866e-46b3-907b-b23eaa9b0c94_1376x864.png&quot; alt=&quot;Illustration of active user pricing model for SaaS billing&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;The Math: Why This Worked (Again)&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;For a company with &lt;strong&gt;100 engineers&lt;/strong&gt;, where only &lt;strong&gt;40 engineers actively pushed commits&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Previous model:&lt;/strong&gt; $4 per user × 100 write-access users = &lt;strong&gt;$400/month&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;New model:&lt;/strong&gt; $8 per user × 40 active users = &lt;strong&gt;$320/month&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Again, the price per user increased, but total spending often decreased or stayed the same. More importantly, it felt &lt;strong&gt;fairer&lt;/strong&gt; to customers.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;The Real Takeaway: Fairness &amp;gt; Exact Pricing Models&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;What we’ve learned is that most customers don’t scrutinize &lt;strong&gt;how much they pay&lt;/strong&gt;—but they deeply care about &lt;strong&gt;why they are paying it&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;💡 &lt;strong&gt;Customers want fairness more than they want cheap pricing.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;They don’t want to pay for people who never use the product.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;They want transparency in billing.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, no matter how much we refine our pricing, &lt;strong&gt;some customers will always question it&lt;/strong&gt;. That’s fine. What matters is that we keep the discussion focused on value—not just pricing mechanics.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Advice for SaaS Startups Navigating Pricing&lt;/strong&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Don’t get stuck in the weeds of perfect pricing.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Focus on maximizing total revenue, not obsessing over per-user logic.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Price increases aren’t scary if you frame them well.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Every time we changed how we counted users, we also raised prices—and it worked fine. You can always grandfather happy customers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;People care more about fairness than numbers.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If customers understand &lt;strong&gt;why&lt;/strong&gt; they’re paying what they’re paying, they’re much less likely to complain.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Pricing is a constant &lt;strong&gt;work in progress&lt;/strong&gt;. We’ll probably keep refining it at Mergify as we grow. But the core lesson is this: &lt;strong&gt;be transparent, focus on fairness, and anchor pricing to the value your product delivers.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you want more on how we think about pricing, I wrote about &lt;a href=&quot;https://julien.danjou.info/blog/saas-and-work-based-pricing&quot;&gt;why we&apos;re sticking with seat-based pricing over work-based models&lt;/a&gt; and the broader &lt;a href=&quot;https://julien.danjou.info/blog/solving-build-vs-buy&quot;&gt;build vs buy dilemma&lt;/a&gt; from the customer&apos;s perspective.&lt;/p&gt;
</content:encoded></item><item><title>Why We Left Heroku</title><link>https://julien.danjou.info/blog/why-we-left-heroku/</link><guid isPermaLink="true">https://julien.danjou.info/blog/why-we-left-heroku/</guid><description>A Tale of Contracts, Challenges, and Change</description><pubDate>Tue, 28 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In January 2023, everything was smooth sailing for Mergify. Our infrastructure was humming along on Heroku, a platform we had trusted for over three years. Heroku was once the go-to choice for startups—simple, reliable, and developer-friendly. We were happy customers, growing steadily and paying our invoices month-to-month.&lt;/p&gt;
&lt;p&gt;Then things started to change.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The Start of a Rocky Relationship&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;In early 2023, Heroku reached out with an enticing offer: transition from month-to-month billing to an annual Heroku Enterprise contract. The deal included significant discounts on everything—dynos (containers), databases, and add-ons—in exchange for a one-year commitment to a certain number of resources.&lt;/p&gt;
&lt;p&gt;We were told we’d be allowed to overuse our resources up to 30% during the year without being bothered—with the understanding that if we grew beyond that, the contract would be adjusted fairly in the next cycle.&lt;/p&gt;
&lt;p&gt;It sounded like a win-win.&lt;/p&gt;
&lt;p&gt;We signed the contract and carried on. For the first year, everything was fine. By the end of 2023, we had indeed surpassed the 30% growth threshold, but Heroku didn’t reach out. The contract auto-renewed, and we moved into 2024 with no issues.&lt;/p&gt;
&lt;p&gt;That was until the automated emails started arriving.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/3299f3cc-673a-415f-b374-6f61fe6201a0_1456x816.webp&quot; alt=&quot;Illustration of automated emails arriving from Heroku about contract changes&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;A Series of Surprises&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;In May 2024, we received an &lt;strong&gt;automated&lt;/strong&gt; email from Heroku. It informed us that the discounts on our containers were being rescinded, effective immediately. Naturally, we contacted Heroku’s support team to understand how that would affect our current contract and were redirected to a new account executive to clarify.&lt;/p&gt;
&lt;p&gt;Their explanation was straightforward: we had doubled our usage, and they wanted us to pay the difference for the current contract term—for the next 9 months.&lt;/p&gt;
&lt;p&gt;While this was unexpected, we decided to comply. We signed an amendment to the contract and paid the outstanding amount. We chalked it up to a policy change, and, as Heroku has been fair so far, we decided to move on.&lt;/p&gt;
&lt;p&gt;But then, in October 2024, another email arrived. This time, Heroku announced that discounts on add-ons, such as PostgreSQL databases and Redis, would also be removed. Once again, we reached out to their team for clarification.&lt;/p&gt;
&lt;p&gt;This conversation, however, was &lt;em&gt;very&lt;/em&gt; different.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/080a6562-068e-43c0-9e8f-61fa6e083f97_1166x1548.png&quot; alt=&quot;Screenshot of the automated Heroku email rescinding add-on discounts&quot; /&gt;
&lt;em&gt;The Heroku automated email we received&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;When Contracts Don’t Matter&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Our current account executive explained that the discounted add-ons we had purchased as part of our original enterprise agreement were no longer “fair” for Heroku.&lt;/p&gt;
&lt;p&gt;Indeed, two years before, our previous account executive offered us a 60% discount on the listed price, which was a power move to make us commit for a whole year to the platform. A practice that worked: we committed to Heroku, and the account executive won a “top deal France SMB” award at Heroku.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/c0c592fa-006d-43e0-9fd3-f0a7448dfb35_1456x816.png&quot; alt=&quot;Illustration of the account executive winning a top deal award for the original Heroku contract&quot; /&gt;&lt;/p&gt;
&lt;p&gt;But now, Heroku wanted us to pay the full price for these services, even though our contract explicitly stated otherwise.&lt;/p&gt;
&lt;p&gt;We reminded them of the terms we agreed to in the contract, but their response was, essentially, “it’s not fair for us anymore.”&lt;/p&gt;
&lt;p&gt;I spent a lot of time trying to understand how getting a few thousand euros more from a loyal startup would impact Salesforce P&amp;amp;L, or how bullying us into paying money we didn’t owe would help our account executive gain respect from their boss, with no luck.&lt;/p&gt;
&lt;p&gt;Despite their efforts to pressure us to pay more, we held firm. A contract is a contract, and we weren’t going to be oppressed into paying for something that wasn’t part of the original agreement.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/1f12cf3e-e8fc-447b-9884-58f90c92808e_1456x816.png&quot; alt=&quot;Illustration of standing firm against unfair contract pressure from a vendor&quot; /&gt;&lt;/p&gt;
&lt;p&gt;However, at this point, it was clear that Heroku was no longer a reliable partner for us. Their lack of stability, constant policy changes, and disregard for contractual terms made it impossible to trust them with our infrastructure.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The Move Away from Heroku&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;By late 2024, we made the decision to move Mergify’s infrastructure to Google Cloud Platform (GCP). Migrating a live product is never easy, but it was the right choice. Heroku, once the pioneer of developer-friendly hosting, had stagnated. The platform’s lack of innovation, combined with its increasingly unpredictable business practices, made it clear that it was time to leave.&lt;/p&gt;
&lt;p&gt;GCP offered the flexibility, scalability, and reliability we needed to grow. The migration was a success, and while it wasn&apos;t a move we originally planned for, it&apos;s one we&apos;re glad we made. Google helped us a lot in moving to their platform, which made the whole process smooth — and it gave us a chance to rethink our entire CI/CD stack, including how we handle &lt;a href=&quot;https://julien.danjou.info/blog/the-challenges-of-merge-queues&quot;&gt;merge queue challenges&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Reflections on Heroku&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Despite the rocky ending, it’s important to acknowledge Heroku’s role in our journey. The platform played a significant part in our early success, providing the simplicity and ease of use that helped us focus on building our product. For small apps and early-stage startups, Heroku can still be a good choice.&lt;/p&gt;
&lt;p&gt;But over time, Heroku failed to evolve. As the tech industry moved forward, Heroku seemed to stand still. Features stagnated, the platform became less relevant, and dealing with them as a customer grew increasingly frustrating. In 2025, it’s hard to recommend Heroku as a reliable choice for scaling companies.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/243ebb5b-c23b-4ace-b314-d44da9537c5d_1456x816.webp&quot; alt=&quot;Illustration of migrating infrastructure from Heroku to Google Cloud Platform&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Advice for Other Startups&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Our experience with Heroku taught us some valuable lessons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Beware of Contracts with Large Companies:&lt;/strong&gt; Big corporations can change their terms, policies, or priorities on a whim. Make sure you fully understand the risks before signing long-term agreements.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Stand Your Ground:&lt;/strong&gt; If a vendor tries to pressure you into unfair terms, don’t be afraid to push back. Contracts exist for a reason. Be ready to jump and save your ass.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Choose Platforms That Grow with You:&lt;/strong&gt; Heroku was perfect for us in the beginning, but as our needs grew, it became clear that we needed a more robust and innovative platform.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For startups navigating similar challenges, remember that your infrastructure choices are critical. Hosting platforms should be partners in your growth, not obstacles.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Leaving Heroku wasn’t an easy decision, but it was the right one for Mergify. We’ve learned a lot from this experience, and we’re excited about what’s ahead with our new infrastructure.&lt;/p&gt;
&lt;p&gt;If you’re a startup considering Heroku—or debating whether to stay or move on—ask yourself this: is your hosting platform helping you scale or holding you back? At the end of the day, it’s all about finding a partner you can trust to grow with you.&lt;/p&gt;
</content:encoded></item><item><title>Remote Work: Great, But Not Perfect</title><link>https://julien.danjou.info/blog/remote-work-great-but-not-perfect/</link><guid isPermaLink="true">https://julien.danjou.info/blog/remote-work-great-but-not-perfect/</guid><description>And why so many companies mandate RTO</description><pubDate>Tue, 21 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Running a fully remote company is an incredible experience, and I say this as someone who’s been working remotely for the past 15 years and managing a remote-first company, &lt;a href=&quot;https://mergify.com&quot;&gt;Mergify&lt;/a&gt;, for the last five. Yet, after a week of in-person collaboration with my team in Toulouse, I feel compelled to reflect on what makes remote work great—and where it falls short.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/2bc3a283-980f-4bcc-9ce0-df9740448fe5_3607x2635.jpeg&quot; alt=&quot;Mergify team collaborating in person during an on-site week in Toulouse&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Let me start by saying this: &lt;strong&gt;remote work is fantastic&lt;/strong&gt;, but it’s not perfect. It’s a trade-off. Depending on your company’s stage, your team’s roles, and the challenges you’re facing, the choice between remote, hybrid, or in-office work can make all the difference.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;What Makes Remote Work Amazing&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The benefits of remote work are undeniable:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Focus and Efficiency&lt;/strong&gt;: Remote work allows individuals to dive deep into their tasks without the distractions of an open office.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Work-Life Balance&lt;/strong&gt;: Cutting out commutes and office hours lets people design their schedules in ways that suit them best.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Access to Global Talent&lt;/strong&gt;: A remote model lets you hire the best person for the job, no matter where they are. At Mergify, this has been invaluable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Flexibility and Autonomy&lt;/strong&gt;: Remote work naturally fosters a culture of trust, where people take ownership of their time and deliverables.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But here’s the catch: &lt;strong&gt;communication in remote work isn’t as fluid as in-office communication.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Why In-Office Communication is Superior&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Face-to-face communication is powerful in ways that virtual communication simply can’t replicate. It’s not just about words—it’s about body language, energy, and subtle nonverbal cues that humans naturally pick up when we’re in the same room.&lt;/p&gt;
&lt;p&gt;When you’re remote, &lt;strong&gt;everything has a higher latency&lt;/strong&gt;. Sure, you can make video calls, send Slack messages, and send emails, but it’s like having a conversation with poor reception. You’ll get your message across, but it’s less fluid and often lacks the nuances that make communication easy and productive.&lt;/p&gt;
&lt;p&gt;Now, this might not be a problem, depending on where you work.&lt;/p&gt;
&lt;p&gt;For startups, this trade-off is especially magnified. When you’re building something new and need constant alignment, the lack of spontaneous coffee chats and hallway conversations slows you down. Good ideas often spark from casual interactions—something much harder to replicate remotely.&lt;/p&gt;
&lt;p&gt;The rollercoaster, that a startup is, requires sharing the energy and adrenaline that you get from awesome news and the comfort that everyone needs when some cloudy day happens. But it’s hard to get the vibe from your coworkers when they are far away. The connection is more difficult to make and maintain.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The Remote vs. Office Matrix&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Consequently, the choice between remote and office work isn’t one-size-fits-all; it’s a matrix of &lt;strong&gt;role&lt;/strong&gt; and &lt;strong&gt;company size&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Large Companies&lt;/strong&gt;: For individual contributors, remote work can be as effective as being in the office. In large organizations, communication often requires structure anyway, and remote tools can handle most of this. However, for managers in these settings, the lack of face-to-face interaction can make it harder to truly understand what’s going on in their teams.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Startups&lt;/strong&gt;: For smaller companies and startups, where speed, creativity, and alignment are critical, remote work becomes trickier. It’s harder to maintain momentum and cohesion when everyone is isolated. Founders and managers need to be proactive, creating systems for communication and connection that compensate for the lack of in-person collaboration.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of course, it’s an oversimplification, and it requires nuance, but you get the gist.&lt;/p&gt;
&lt;p&gt;The matrix also applies to roles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Individual contributors&lt;/strong&gt;, particularly in large companies, the need to be physically present in an office can feel unnecessary. Their work often revolves around tasks that require focus rather than constant communication or managing teams. Sitting behind a desk all day in an office doesn’t necessarily enhance their productivity or add value to their contributions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On the other hand, &lt;strong&gt;managers&lt;/strong&gt;, especially in startups, face unique challenges with remote setups. Their roles demand frequent interaction, gauging team dynamics, and fostering collaboration. Without the ability to observe non-verbal cues or engage in casual, spontaneous conversations, understanding the team’s morale and addressing issues proactively becomes significantly harder.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The same goes for seniority:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Junior employees&lt;/strong&gt; typically require more hands-on management, regular feedback, and frequent check-ins. Without the autonomy or experience to navigate challenges independently, they benefit greatly from close guidance and structured oversight.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In contrast, &lt;strong&gt;senior employees&lt;/strong&gt; tend to be highly autonomous. They often need minimal direction, excelling at managing their own work and making decisions. They are comfortable raising issues or seeking input when needed, allowing them to operate effectively with little interaction from their managers.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is one of the reasons there has been so much vociferation against &lt;strong&gt;Return to Office (RTO)&lt;/strong&gt; mandates promulgated in recent years, especially in &lt;strong&gt;larger companies and amongst senior individual contributors&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/8f13ea90-95d8-4c4c-b61a-33f9b00e2db0_1343x1579.png&quot; alt=&quot;Chart showing remote work preferences by role seniority and company size&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The Mergify Experience: Remote, But Not Alone&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;At Mergify, &lt;a href=&quot;https://blog.mergify.com/embracing-remote-work-how-we-built-mergify-as-a-successful-asynchronous-company/&quot;&gt;we’ve been fully remote from the start&lt;/a&gt;, and we’re committed to staying that way. But we know it’s not without challenges. Here’s how we’ve managed the trade-offs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Intentional Connection&lt;/strong&gt;: We schedule regular virtual coffee breaks to foster camaraderie and maintain a sense of community.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Quarterly On-Sites&lt;/strong&gt;: Every few months, we bring the team together in person. Our recent week in Toulouse was a reminder of how valuable these moments are—not just for productivity but for bonding as a team. Sharing meals, brainstorming in person, and simply spending time together are irreplaceable experiences. Nothing beats your team being yelled at by the game master of an escape game for having hacked your way around the solution, in true startup spirit. 😅&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Focus on Proactivity&lt;/strong&gt;: Remote work requires deliberate communication. Everyone on the team needs to take the initiative to keep each other informed and aligned.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hiring for Autonomy&lt;/strong&gt;: A remote model of self-motivated, independent individuals who excel without constant oversight. Building a team with these traits has been crucial for our success. Not everyone is made for remote work. (More on this in &lt;a href=&quot;https://julien.danjou.info/blog/not-just-a-job-its-a-ride&quot;&gt;Not Just a Job, It&apos;s a Ride&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/47001332-a007-4617-ac0f-b4b394249335_1376x864.png&quot; alt=&quot;Illustration of hiring autonomous people for remote work success&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;&lt;strong&gt;RTO and the Future of Work&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The current trend of companies mandating a Return to Office reflects the challenges of remote work—particularly for management. It’s easier to see what’s happening, build trust, and foster collaboration in person. Yet, for many roles and organizations, remote work remains a superior option.&lt;/p&gt;
&lt;p&gt;The reality is that no single model is perfect. For some, the trade-offs of remote work are worth it. For others, the benefits of in-office collaboration outweigh the flexibility of remote setups. &lt;strong&gt;What matters most is that companies recognize these trade-offs and build systems that suit their unique needs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Remote work is here to stay, but it’s not a panacea. It’s a trade-off between flexibility and connection, efficiency and spontaneity. At Mergify, we’ve embraced remote work with open eyes—recognizing its strengths while finding ways to address its weaknesses. Whether remote, hybrid, or in-office, the key is to adapt, experiment, and keep evolving.&lt;/p&gt;
&lt;p&gt;What’s your take on the remote vs. office debate? Let me know in the comments or reach out—I’d love to hear how others are navigating this shift.&lt;/p&gt;
</content:encoded></item><item><title>Reflecting on 2024</title><link>https://julien.danjou.info/blog/reflecting-on-2024/</link><guid isPermaLink="true">https://julien.danjou.info/blog/reflecting-on-2024/</guid><description>A Year of Growth, Change, and Learning at Mergify</description><pubDate>Tue, 07 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As 2025 begins, I’m taking a moment to reflect on an eventful 2024—a year of evolution, challenges, and new beginnings for me and &lt;a href=&quot;https://mergify.com&quot;&gt;Mergify&lt;/a&gt;. Building a bootstrapped company comes with its own unique highs and lows, and 2024 was no exception. Here’s what the past year taught me and how it has shaped the future of Mergify.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Keeping Mergify Thriving in a Changing Market&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Surviving and thriving as a bootstrap startup is always worth celebrating, especially in a niche like ours. In 2024, we doubled down on what makes Mergify special: our &lt;strong&gt;&lt;a href=&quot;https://mergify.com/product/merge-queue&quot;&gt;Merge Queue&lt;/a&gt;&lt;/strong&gt; product, which remains the best in the market for helping engineering teams manage pull request workflows.&lt;/p&gt;
&lt;p&gt;That said, we also recognized our limitations. While we’ve always been proud of our technology, we realized this year that &lt;strong&gt;having great tech isn’t enough&lt;/strong&gt;. For years, Mergify was more of a tech-driven company than a product-focused one. This year, we worked hard to change that, shifting our mindset to prioritize product design, usability, and scalability.&lt;/p&gt;
&lt;p&gt;The advancement of competitors, such as GitHub, forced us to rethink our strategy. We’ve been evolving in a niche for years, and it’s now time for us to expand our vision beyond what we’ve been doing so far.&lt;/p&gt;
&lt;p&gt;2024 also brought some tough lessons about the realities of the market. We initially decided to double down on marketing in late 2022, deploying our efforts during all of 2023. However, after the startup market crash of late 2022, we spent much of 2023 navigating a challenging environment where companies were hesitant to adopt new tools. As that trend continued into early 2024, we ultimately decided to &lt;strong&gt;scale back our marketing efforts&lt;/strong&gt; and rethink how we approach growth.&lt;/p&gt;
&lt;p&gt;The truth is that marketing alone can’t solve every problem—especially in a niche like ours. Instead, we’re focusing on building the best products possible and letting our work speak for itself. This approach has already started to pay off, and we’re more confident than ever in Mergify’s future.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;A Shift Toward Product and Design&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Consequently, one of the pivotal moments in 2024 was hiring a &lt;strong&gt;designer&lt;/strong&gt;—a first for Mergify. This move sparked a transformation in how we approached our product. It wasn’t just about solving technical challenges anymore; it was about creating an experience developers love.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/db1e31b2-b1ec-4bfa-8995-c64030ac9ac2_1378x953.png&quot; alt=&quot;Screenshot of the new Mergify dashboard design&quot; /&gt;
&lt;em&gt;New Mergify design&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This new focus led to a complete redesign of our branding and dashboard, making it easier than ever for teams to onboard and use Mergify. It also paved the way for &lt;strong&gt;new products&lt;/strong&gt; like &lt;strong&gt;&lt;a href=&quot;https://mergify.com/product/merge-protections&quot;&gt;Merge Protections&lt;/a&gt;&lt;/strong&gt;, a tool for managing repository freezes and policies. This was the first product we built with a product-driven mindset from the ground up, and it’s already gaining traction with customers.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Back to Founder Mode&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;In 2024, I found myself returning to what is now called “&lt;a href=&quot;https://paulgraham.com/foundermode.html&quot;&gt;founder mode&lt;/a&gt;.” For the first time in years, I rolled up my sleeves and dove back into &lt;strong&gt;coding and product development&lt;/strong&gt;. Writing Python again, designing architecture, new workflows, and collaborating directly with the team reminded me of the early days of Mergify—and how much I enjoy building things.&lt;/p&gt;
&lt;p&gt;This hands-on approach was fueled in part by the rise of &lt;strong&gt;AI tools&lt;/strong&gt;, which have &lt;a href=&quot;https://julien.danjou.info/blog/connecting-the-dots-with-ai&quot;&gt;transformed how we work&lt;/a&gt;. From speeding up R&amp;amp;D to enhancing productivity, AI has helped us stay agile and efficient as we tackle big challenges in CI/CD.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Doubling Down on R&amp;amp;D&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Speaking of challenges, &lt;strong&gt;research and development&lt;/strong&gt; was a major focus for us in 2024. We spent a lot of time exploring how to solve some of the toughest problems in CI/CD, like &lt;strong&gt;flaky tests, CI failures, and observability issues&lt;/strong&gt;. These pain points resonate deeply with our customers, and we’re excited to bring solutions to market in 2025.&lt;/p&gt;
&lt;p&gt;Our work so far has been a team effort, but it’s also required stepping outside our comfort zone. We’ve been engaging more directly with developers, learning from their frustrations and workflows, and using those insights to shape our next big product.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;On a Personal Note&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Outside of Mergify, 2024 was a year of rediscovery for me. I started writing again, leaning on tools like GPT to help me produce regular content and share my thoughts with a wider audience. Writing has always been a way for me to reflect, and having the discipline to do it consistently has been rewarding.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/3d683497-9731-4495-aa15-f08f944bde7f_2959x3945.jpeg&quot; alt=&quot;Photo taken by Julien&apos;s kids learning photography&quot; /&gt;
&lt;em&gt;I’m also trying to teach my kids to take photographs&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I also continued my work as a &lt;strong&gt;business angel&lt;/strong&gt;, supporting other startups and sharing what I’ve learned from building Mergify. While balancing this with my responsibilities at Mergify can be challenging, it’s incredibly fulfilling to help others navigate the ups and downs of entrepreneurship.&lt;/p&gt;
&lt;p&gt;I continued publishing episodes of &lt;a href=&quot;https://nomdunpipeline.com/&quot;&gt;Nom d’un Pipeline !&lt;/a&gt;, my French CI/CD-themed podcast. This has been a tremendous way to meet new people and learn stuff. I really enjoy the exercise of podcasting, and I’m looking for an excuse to start a new one this year.&lt;/p&gt;
&lt;p&gt;I also recorded several episodes as a guest, &lt;a href=&quot;https://www.ifttd.io/episodes/ci-cd&quot;&gt;one in French in IFTTD&lt;/a&gt;, &lt;a href=&quot;https://www.iot-valley.fr/podcast/37-les-perspectives-sur-lavenir-de-la-tech-avec-julien-danjou&quot;&gt;another one in French and in Toulouse on the future of tech&lt;/a&gt;, &lt;a href=&quot;https://podcasts.apple.com/fr/podcast/14-julien-danjou-mergify/id1691308698?i=1000644808297&quot;&gt;another one in French on The Cloud Screener&lt;/a&gt;, &lt;a href=&quot;https://www.pythonshow.com/p/36-serious-python-with-julien-danjou&quot;&gt;one on The Python Show&lt;/a&gt;, and, finally, &lt;a href=&quot;https://www.youtube.com/watch?v=Fd2iQD7Rfug&quot;&gt;one with saas.group&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Looking Ahead to 2025&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;As I look to the year ahead, I’m more excited than ever about what’s next for Mergify. We’re working on &lt;strong&gt;new products&lt;/strong&gt; that tackle critical pain points in CI/CD, and we’re committed to helping developers ship faster and with less friction. At the same time, we’re staying true to our roots as a bootstrap startup—focused, agile, and always learning.&lt;/p&gt;
&lt;p&gt;2024 was a year of growth in every sense of the word. It challenged us to think differently, to embrace new ideas, and to keep pushing forward. As we enter 2025, I’m grateful for the lessons we’ve learned, the customers we serve, and the incredible team that makes it all possible.&lt;/p&gt;
&lt;p&gt;Here’s to another year of building, learning, and growing.&lt;/p&gt;
</content:encoded></item><item><title>The Collapse of Social Platforms</title><link>https://julien.danjou.info/blog/the-collapse-of-social-platforms/</link><guid isPermaLink="true">https://julien.danjou.info/blog/the-collapse-of-social-platforms/</guid><description>A prediction for 2030</description><pubDate>Tue, 17 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It’s the end of the year, so I’ll write about something more theoretical that has been on my mind those last few days.&lt;/p&gt;
&lt;p&gt;What happens when the line between human and AI content creators vanishes completely? We’re closer to that reality than you might think.&lt;/p&gt;
&lt;p&gt;I was out running a few days ago listening to a tech podcast, &lt;a href=&quot;https://siliconcarne.substack.com/&quot;&gt;Silicon Carne&lt;/a&gt;. There was an interesting debate around content creation and how platforms like YouTube will kill TV. I’m not sure the root of the talk was that challenging; TV seems already a thing of the past at this stage. But as they started to talk about AI, things started to get interesting.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/a9e23696-5aa6-4048-a7ae-af69a8a84b64_2000x1256.jpeg&quot; alt=&quot;Illustration of the blurring line between human and AI content creators&quot; /&gt;&lt;/p&gt;
&lt;p&gt;People often have limited visions of what’s possible, shaped by their beliefs and ideas about what’s acceptable.&lt;/p&gt;
&lt;p&gt;Most of the discussion revolved around how AI would be able to create content, how it would be used to help producers and content creators, and what it would mean for platforms and consumers.&lt;/p&gt;
&lt;p&gt;Based on that, the debate continued about how much AI would be acceptable in content creation on platforms.&lt;/p&gt;
&lt;p&gt;I think this is very short-sighted.&lt;/p&gt;
&lt;h2&gt;What’s Already Happening&lt;/h2&gt;
&lt;p&gt;You don’t have to look far to see AI being used in content production; that’s a fact. But it’s still very human-driven and AI-assisted. There are a lot of tech limitations for now that prevent pushing the throttle to the max, but it is certain that those limitations will go away very soon. Look at what OpenAI is building with &lt;a href=&quot;https://openai.com/sora/&quot;&gt;Sora&lt;/a&gt;, and you’ll have a glimpse of the future.&lt;/p&gt;
&lt;p&gt;People are already leveraging this tech to move to the next step: creating content, communities, and creators that do not exist in real life. Instagram and OnlyFans are seeing a tsunami of AI-based girls managed by digital pimps. Does it work? It sure does; look at the numbers.&lt;/p&gt;
&lt;p&gt;This is where many people start to get confused and want to draw a line based on morale or their beliefs that this model will not be applicable to “regular” content creation.&lt;/p&gt;
&lt;p&gt;I believe this is false; it’s already happening.&lt;/p&gt;
&lt;h2&gt;A Glimpse into the Future&lt;/h2&gt;
&lt;p&gt;People often argue that having AI-generated content from a content creator would feel inauthentic and that they wouldn’t watch it. I say that this is having a very high opinion of your brain and little faith in the evolution of AI.&lt;/p&gt;
&lt;p&gt;What if I told you that MrBeast did not exist? You’d say, of course, he does! Really? How can you know he exists? Did you ever meet him in real life? Did you ever talk to him?&lt;/p&gt;
&lt;p&gt;What if, tomorrow, you’d connect to YouTube and see 10 new MrBeast videos with fancy new ideas that’d fit your taste and be very appealing to your brain? They might or might not be AI-generated; in any case, you’d have a good time.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/25d2cc69-bd29-4a95-b9c5-6a0f8b55e502_1376x864.png&quot; alt=&quot;Illustration of AI-generated content creators indistinguishable from real people&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now, let’s take a step back and imagine having a brand new content creator everyone’s talking about. Nobody heard of them before. You watch the content, and you like it. Does this person really exist, or is it just an AI? How would you ever know? There might be rumors that a friend of a friend met him in a restaurant… but is that the reality?&lt;/p&gt;
&lt;p&gt;At some point, there will be no way to know if a content creator is a real person or not. As time passes and technology evolves, it will be close to impossible to distinguish human creation from AI creation — &lt;a href=&quot;https://julien.danjou.info/blog/the-synthetic-wave-is-already-here&quot;&gt;the synthetic wave is already here&lt;/a&gt;. This is what most people don&apos;t want to believe because it shuffles too much their current reality.&lt;/p&gt;
&lt;p&gt;Believe it or not, it’s happening.&lt;/p&gt;
&lt;h2&gt;How Platform Might Crash&lt;/h2&gt;
&lt;p&gt;The ability to generate endless streams of AI-driven content will undoubtedly transform platforms like Instagram, YouTube, and LinkedIn. In the short term, the appeal of hyper-tailored, dopamine-driven content may captivate users and drive unprecedented engagement.&lt;/p&gt;
&lt;p&gt;But at what cost?&lt;/p&gt;
&lt;p&gt;As AI-generated content floods these platforms, the lines between human connection and algorithmic interaction will blur. The authenticity that once set content creators apart—real people sharing real experiences—will be diluted in a sea of indistinguishable, machine-generated personas. Even if platforms introduce measures like “human-verified” badges, the deeper question remains: will people still care? If the content entertains, informs, or inspires, does its origin matter?&lt;/p&gt;
&lt;p&gt;This shift could erode one of social media&apos;s fundamental purposes: fostering connection. If users begin to see platforms as spaces dominated by machines rather than humans, the sense of community these platforms once provided may crumble. The allure of authentic interaction—the very reason social media exploded in the first place—could fade, leaving behind a world where “social” media is anything but social.&lt;/p&gt;
&lt;p&gt;This trend raises profound questions in the broader societal context. Will our online spaces become environments where we primarily engage with algorithms instead of people? As AI infiltrates every email, phone call, and comment, will technology become a tool for connection or a barrier to it?&lt;/p&gt;
&lt;p&gt;Perhaps this is where the pendulum swings back to real life. In a world saturated with AI interactions, the simplest moments of human connection—a conversation over coffee, a shared laugh, or a face-to-face debate—might become rare and precious. Paradoxically, as AI dominates the digital realm, it could reignite our desire for genuine human interaction in the physical world.&lt;/p&gt;
&lt;p&gt;Until then, the question isn’t whether AI-generated content will dominate—it’s how we, as creators and consumers, will adapt and what we’ll choose to value in an increasingly artificial landscape.&lt;/p&gt;
&lt;p&gt;My prediction is that real life will be the only place you’ll have left to interact with real humans.&lt;/p&gt;
&lt;p&gt;Until robots take over, of course.&lt;/p&gt;
</content:encoded></item><item><title>Aligning Project Management with Company&apos;s Values</title><link>https://julien.danjou.info/blog/aligning-project-management-with/</link><guid isPermaLink="true">https://julien.danjou.info/blog/aligning-project-management-with/</guid><description>Aligning Project Management with Company&apos;s Values</description><pubDate>Tue, 10 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When Mehdi and I co-founded Mergify, we didn’t just set out to create a great product—we wanted to build a company that reflected our values. Part of that journey has been rethinking project management, a task informed by years of working in organizations where “Agile” had become synonymous with bureaucracy. What started as a flexible, team-oriented methodology often felt bogged down by rituals that added complexity without delivering real results.&lt;/p&gt;
&lt;p&gt;Over the past year, we’ve refined our project management approach at Mergify, shaping it to fit not only the needs of our team but also our belief in simplicity, ownership, and autonomy. Here’s the story of how we got there—and why building a workflow that aligns with your values matters as much as the work itself.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;My Journey Through Agile Overload&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;When I look back at my experiences with Agile in various organizations, one story sticks out. Early in my career, I joined a team at &lt;a href=&quot;https://redhat.com&quot;&gt;Red Hat&lt;/a&gt;, one of the most functional, productive groups I’ve ever worked with. We didn’t rely on heavy Scrum processes; instead, we used a lightweight Kanban board and had stand-ups over &lt;a href=&quot;https://en.wikipedia.org/wiki/IRC&quot;&gt;IRC&lt;/a&gt;. It wasn’t fancy, but it worked. We focused on the work, not the process.&lt;/p&gt;
&lt;p&gt;Contrast that with some other teams I observed. One team, with over 20 members, struggled to maintain a sense of ownership. The &lt;a href=&quot;https://www.theguardian.com/technology/2018/apr/24/the-two-pizza-rule-and-the-secret-of-amazons-success&quot;&gt;two-pizza rule&lt;/a&gt;, famously touted by Jeff Bezos, couldn’t have been more relevant here: a team too big to share two pizzas is often too big to stay effective. Communication is complicated, and the sense of ownership disappears.&lt;/p&gt;
&lt;p&gt;Yet management decided to unify everyone under a single Scrum process, complete with daily stand-ups, sprints, retrospectives, and poker planning.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/4c8fc2f7-0ba1-4d1f-8bd8-35af6390654e_1376x864.webp&quot; alt=&quot;Illustration of a large team struggling under heavy Scrum processes&quot; /&gt;&lt;/p&gt;
&lt;p&gt;That did not fix the problem of the 20-person team. Even my high-functioning team began to falter under the weight of unnecessary rituals.&lt;/p&gt;
&lt;p&gt;It was a powerful lesson: the process isn’t inherently good or bad but must serve the people doing the work.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Starting Mergify with a Blank Slate&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;When we started Mergify, we wanted to avoid the traps of over-engineering our processes. We began with almost no structure—just Slack messages and quick syncs to stay aligned. As the team grew, we added a daily stand-up. For a remote-first company, these short, synchronous check-ins were critical for maintaining a shared understanding, even as most of our work remained asynchronous.&lt;/p&gt;
&lt;p&gt;Instead of following Agile dogma, we opted for a Kanban approach. Tasks moved naturally across the board with minimal friction. We didn’t bother with two-week sprints or strict velocity tracking; we let the workflow dictate the process.&lt;/p&gt;
&lt;p&gt;And it worked; for a while.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Why Lightweight Isn’t Always Enough&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Over time, cracks began to appear. One issue was ownership: who was responsible for creating cards on the Kanban board? Engineers who weren’t involved in defining tasks felt disconnected from the problem, treating the cards as instructions rather than opportunities to solve meaningful challenges. The person creating the card and the person doing the work weren’t always on the same page.&lt;/p&gt;
&lt;p&gt;Another challenge was the endless backlog without a clear sense of what we were building or why; tasks accumulated, and the act of moving cards felt less like progress and more like treading water. The team craved a greater sense of accomplishment—a way to see their impact beyond the daily grind.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Evolving to a Project-Driven Workflow&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;To address these issues, we introduced a project-driven layer to our workflow. Projects became our new organizing principle: scoped pieces of work that could be completed in two to four weeks. Each project was defined by three key elements: a &lt;strong&gt;brief&lt;/strong&gt;, a &lt;strong&gt;lead&lt;/strong&gt;, and a &lt;strong&gt;deadline&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;1. &lt;strong&gt;The Brief&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;The brief outlined the problem, the goals, and the context for the project. It provided enough structure to guide the engineer while leaving room for creativity. Engineers weren’t just implementers—they were collaborators, shaping the solution as they worked.&lt;/p&gt;
&lt;p&gt;2. &lt;strong&gt;The Lead&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Every project had a designated lead who was responsible for tracking progress and ensuring the work stayed on course. This wasn’t about assigning blame; it was about having a clear point of contact who could raise blockers, answer questions, and coordinate efforts.&lt;/p&gt;
&lt;p&gt;3. &lt;strong&gt;The Deadline&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Deadlines were less about pressure and more about focus. They encouraged engineers to make trade-offs, prioritize effectively, and avoid over-engineering. If something couldn’t be completed within the timeframe, we adjusted the scope or deferred less critical elements.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/ad9c358b-c1d4-4e07-ba08-e11ebf84bd06_1376x864.webp&quot; alt=&quot;Illustration of project-driven workflow with briefs, leads, and deadlines&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;What We Gained&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The shift to project-driven work transformed how we operated. It gave engineers a sense of ownership and allowed us to ship faster, avoiding the dreaded “tunnel effect” where nothing tangible gets delivered for months. It also helped us align our priorities, ensuring that every project contributed meaningfully to our goals.&lt;/p&gt;
&lt;p&gt;This system wasn’t just about productivity but about creating a culture where engineers felt empowered and connected to their work. It reinforced our belief that processes should serve people, not the other way around.&lt;/p&gt;
&lt;p&gt;A few people I talked about our system thought it resembled the &lt;a href=&quot;https://basecamp.com/shapeup&quot;&gt;Shape Up&lt;/a&gt; methodology from Basecamp. I think it does have similarities, except that we’re a small company, meaning we don’t have enough teams to model it exactly yet. But that’s definitely a methodology that resonates with us.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/dea4170d-6f82-4e9f-9d0d-523c0ac8f4e5_2000x729.png&quot; alt=&quot;Diagram of the Shape Up methodology from Basecamp&quot; /&gt;
&lt;em&gt;The Shape Up methodology&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Looking back, the changes we made weren’t just about fixing problems—they were about staying true to our values. At Mergify, we believe in autonomy, ownership, and simplicity, and our workflow reflects those principles.&lt;/p&gt;
&lt;p&gt;If you’re struggling with your own project management processes, ask yourself: do they serve your team’s needs, or are they just there because “that’s how it’s done”? (I wrote about a related frustration in &lt;a href=&quot;https://julien.danjou.info/blog/the-problem-with-okrs-isnt-okrs&quot;&gt;The Problem with OKRs Isn’t OKRs&lt;/a&gt;.) The best workflows aren’t the most popular—they’re the ones that align with your culture and empower your people to do their best work.&lt;/p&gt;
&lt;p&gt;At Mergify, we’re proud of the system we’ve built, and we’re excited to keep evolving it as we grow.&lt;/p&gt;
</content:encoded></item><item><title>SaaS and Work-based Pricing</title><link>https://julien.danjou.info/blog/saas-and-work-based-pricing/</link><guid isPermaLink="true">https://julien.danjou.info/blog/saas-and-work-based-pricing/</guid><description>Is this the future?</description><pubDate>Tue, 19 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Despite the rising popularity of work-based pricing in SaaS, Mergify is sticking with seat-based pricing—for now. Here’s why we believe it’s the right choice for our product and our customers, and their ability to budget with confidence&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The Work-Based Pricing Trend&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Following the latest SaaS pricing trends is tempting, especially when they align so well with a powerful concept: customers pay in proportion to the value they receive. In recent months, “work-based” pricing has gained traction across the industry, especially in AI-driven applications where you pay per task completed. It’s a straightforward exchange: resolve a customer’s problem and receive $1.&lt;/p&gt;
&lt;p&gt;Simple, clear, and highly attractive.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/bde84986-3269-430e-a397-d7970e372fbd_1376x864.webp&quot; alt=&quot;Illustration of work-based pricing in SaaS&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When we first considered revisiting Mergify’s pricing for next year, work-based pricing seemed like a model worth exploring. Imagine a setup where every task accomplished by Mergify correlated directly to the value we delivered to our customers. More value equals more payment, a transparent exchange that clients find easy to understand. But, as we dove deeper, we realized that the nature of Mergify’s work doesn’t fit so neatly into this model.&lt;/p&gt;
&lt;p&gt;And that might be true for your SaaS as well.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The Value Clarity of Work-Based Pricing&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Work-based pricing makes perfect sense for certain SaaS products. Consider the AI-driven customer support solutions rolling out recently, like &lt;a href=&quot;https://www.salesforce.com/agentforce/pricing/&quot;&gt;Salesforce’s $2-per-conversation approach&lt;/a&gt; or &lt;a href=&quot;https://www.intercom.com/help/en/articles/8205718-fin-ai-agent-resolutions&quot;&gt;Intercom’s Finn and its 0.99$ per resolved conversation&lt;/a&gt;. The pricing here is inherently appealing because it aligns precisely with the customer’s perception of value: for every problem resolved, they see a clear, direct benefit to their end users, who leave satisfied and engaged.&lt;/p&gt;
&lt;p&gt;This clarity makes it easy for a customer to decide—they’re paying to solve a specific pain point for their users, and each solved interaction has a measurable outcome. The more conversations are solved, the more they pay; it feels like a no-brainer.&lt;/p&gt;
&lt;p&gt;Customers see exactly where their money is going, and it scales beautifully alongside their growth: the more users they have, the more problems they have, and the more value you can provide. But also: the more users they have, the more money they have, so they’re happy with giving you a part of it. Everything can grow at the same rate, from the customer’s business size to the value your SaaS provides.&lt;/p&gt;
&lt;p&gt;This reminds me of the early days of cloud computing when Amazon Web Services introduced usage-based billing. The more your business grew, the more infrastructure you used, and the higher the bill. It made perfect sense, and that was one of the reasons for its success and early adoption by startups.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/8928f8ae-0c2c-4992-8a82-0c7739b205be_1376x864.png&quot; alt=&quot;Illustration of usage-based billing scaling with business growth&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Why Work-Based Pricing Doesn’t Always Fit&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;At Mergify, we aimed to find the same direct correlation between our value and our pricing model. However, our reality is different: the value we provide is primarily to software engineers who use Mergify to streamline their CI/CD workflows. It’s an incredibly powerful tool, but it doesn’t impact our customers’ end-users in an obvious way. This makes work-based pricing challenging because we’re one step removed from the end-user experience — and, therefore, from the business.&lt;/p&gt;
&lt;p&gt;We brainstormed possible approaches. Perhaps we could charge per pull request made by developers. After all, that’s a major part of what Mergify automates and where it provides value. But we immediately saw a problem: this model could encourage the wrong behavior. If every pull request carries a charge, teams might try to reduce the number of pull requests to save on costs, potentially compromising code quality. As engineers ourselves, we value clean, &lt;a href=&quot;https://en.wikipedia.org/wiki/Atomic_commit&quot;&gt;atomic commits&lt;/a&gt; and easy reviews. A per-pull-request charge could discourage these practices, creating friction between the optimal workflow and our pricing model.&lt;/p&gt;
&lt;p&gt;Another challenge with work-based pricing is the difficulty it presents for customers when predicting their usage. Most organizations set budgets a year in advance, and it’s nearly impossible for a team to accurately estimate the number of pull requests or jobs they’ll need in a year. This unpredictability makes budgeting stressful and challenging, especially for engineers seeking approval for software expenses. With seat-based pricing, customers know their costs upfront, which aligns much better with annual budget planning.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/37914459-6600-46b0-a064-bd6144fb49fe_1376x864.png&quot; alt=&quot;Illustration of the challenges of per-unit pricing for developer tools&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We also considered a per-job charge based on CI (Continuous Integration) runs, but this quickly ran into similar issues. Running a high volume of CI jobs often results in better software quality. Charging per job could lead to reduced testing—a problematic incentive in an industry where quality matters deeply. CI providers charge per job because of the required computing power, but in Mergify’s case, we don’t incur comparable costs. So, a per-job charge wouldn’t reflect our real costs and could end up discouraging best practices.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The Challenge of Finding the Right Fit&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;In a perfect world, we’d find a model where Mergify’s pricing scaled directly with the customer’s perceived value. However, we found that our usage patterns, like many SaaS, do not align with the benefits of the work-based approach. To truly capture the value Mergify provides, we realize that seat-based pricing continues to be our best option, at least for now.&lt;/p&gt;
&lt;p&gt;By charging per user (seat), we avoid influencing developer behavior, allowing them to use Mergify to improve their workflow without second-guessing how often they use it. We also want to avoid the stress of unpredictable costs. Seat-based pricing allows our clients to budget accurately and avoid unexpected expenses, aligning our pricing model with their planning cycles and offering peace of mind. As we continue to build Mergify, our goal remains to be the trusted tool in the hands of developers.&lt;/p&gt;
&lt;p&gt;Right now, that means a seat-based model, which keeps our focus where it belongs—on supporting teams to do their best work, no strings attached.&lt;/p&gt;
&lt;p&gt;This decision wasn&apos;t easy, and we might revisit it in the future. Pricing is a delicate balance between the customer&apos;s experience, the product&apos;s value, and the company&apos;s needs. I wrote more about &lt;a href=&quot;https://julien.danjou.info/blog/saas-pricing-is-hard&quot;&gt;our full pricing journey at Mergify&lt;/a&gt; — from copying GitHub&apos;s model to active user billing.&lt;/p&gt;
</content:encoded></item><item><title>The Engineer’s Dilemma: What We Did Right at Mergify</title><link>https://julien.danjou.info/blog/the-engineers-dilemma-what-we-did/</link><guid isPermaLink="true">https://julien.danjou.info/blog/the-engineers-dilemma-what-we-did/</guid><description>A classic mistake that many tech founders make.</description><pubDate>Tue, 05 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In the early days of Mergify, the journey Mehdi and I embarked on wasn’t unique. In fact, it’s a tale as old as time for engineer founders: a couple of smart engineers, passionate about technology, with an exciting vision to revolutionize their space. We had everything we needed—or so we thought: the knowledge, the technical expertise, and the drive to build something incredible.&lt;/p&gt;
&lt;p&gt;This is where the story of Mergify begins, but what often happens next is a classic mistake that many tech founders make. They build a beautiful, feature-packed product that doesn’t solve a real problem—or even worse, it solves a problem no one has. This is the hard lesson that too many engineers learn too late, and it could have easily been us.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/21e071bd-e668-4aa6-a22d-3c96ff16fa8a_2000x1256.jpeg&quot; alt=&quot;Illustration of engineer founders building a startup together&quot; /&gt;&lt;/p&gt;
&lt;p&gt;But we managed to steer our ship in a different direction, and looking back, there are key things we did right. Let me take you through that journey.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The Temptation of the Tech&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;For any engineer, there’s nothing more fun than building. The thrill of creating a new feature, optimizing your product, or pushing out updates can become intoxicating. Mehdi and I felt this pull strongly when we started Mergify. We had big ideas, a packed roadmap, and technical solutions that we were eager to implement.&lt;/p&gt;
&lt;p&gt;But here’s the thing: technology, while critical, is only a part of building a successful SaaS business. We could have easily fallen into the trap of focusing solely on the tech and neglecting the most important piece of the puzzle: the customer.&lt;/p&gt;
&lt;p&gt;We both had to confront the reality that building an amazing product wasn’t enough. If we didn’t speak to our customers, understand their pain points, and really get to the heart of their problems, we were going to fail.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This is where so many engineer-founded startups stumble.&lt;/strong&gt; They fall in love with their technology rather than falling in love with solving the customer’s problem. Luckily, we managed to recognize this early on.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Engineers Need to Talk to People&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Talking to customers doesn’t come naturally to most engineers—it didn’t for us either—but it was a necessary step. While it’s tempting to stay behind your keyboard, tweaking code or adding features, the real magic happens when you step out and listen to what your customers are saying. What do they struggle with? What would make their lives easier? What’s keeping them up at night?&lt;/p&gt;
&lt;p&gt;I remember one story about two bright engineers who reached out to me on LinkedIn, seeking advice. We decided to meet at a bar. They had spent three years working on a fantastic piece of technology but hadn’t seen any traction. Why? because they hadn’t built it with a customer in mind. The tech was solid, but it didn’t solve any real problem. They hadn’t spent time talking to users, and so the product existed in a vacuum.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;You&apos;ve got to start with the customer experience and work backwards to the technology&lt;/em&gt; — Steve Jobs (1997)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;With Mergify, we knew that if we were going to build something with a lasting impact, we needed to constantly engage with our community and understand their problems. It wasn’t enough to have a great piece of technology—we had to have a great solution to a real problem.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Shifting Roles for Success&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;One of the smartest things Mehdi and I did early on was divide our roles clearly. We knew that if both of us were deep in the tech, Mergify would never succeed. So, while Mehdi stayed focused on building the product, I took on the role of sales, marketing, and customer interaction.&lt;/p&gt;
&lt;p&gt;For an engineer, stepping into these roles can be uncomfortable at first. Sales? Marketing? Communication? These aren’t things they teach you in computer science class. But it was a necessary shift and one that paid off.&lt;/p&gt;
&lt;p&gt;I drew from my experience selling self-published books. I knew that just because you write something doesn’t mean people will read it. You have to market it, spread the word, and get it into the hands of people who need it. The same principle applied to Mergify. We couldn’t just build features and expect users to come flocking. We had to sell it, promote it, and make it known. This is still something we need to do to this day.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;The Hard Truth&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The truth is, you can have the best technology in the world, but if you don’t have customers, it’s worthless.&lt;/p&gt;
&lt;p&gt;I remember another encounter at a wedding, where the bride&apos;s father introduced me to his nephew—a tech entrepreneur. The moment I heard him describe his startup, I already knew what was wrong. “You’re not selling anything, are you?” I asked. The bride’s father looked at me, astonished by the boldness of my assumption. And sure enough, he wasn’t. He and his co-founder, both engineers, had spent their time adding features instead of learning how to sell their product. He admitted that I was not the first to tell them it was one recipe for a disaster.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/0680ef2e-f6d0-48f5-ae52-ed0422a8e382_2000x1256.jpeg&quot; alt=&quot;Illustration of engineers who built great tech but forgot to sell it&quot; /&gt;&lt;/p&gt;
&lt;p&gt;At Mergify, we avoided that trap. We recognized early on that while the tech needed to be solid, the success of our business depended on our ability to market and sell it.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;What We Did Right&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;So, what did we do right at Mergify? We talked to our customers, really talked to them. We asked questions, learned about their challenges, and made sure we were solving their pain points. We didn’t fall in love with our technology; we fell in love with the problem. And most importantly, we divided and conquered. Mehdi stayed on the tech while I trained myself in the art of sales, marketing, and product management.&lt;/p&gt;
&lt;p&gt;These steps weren’t easy, requiring us to step outside our comfort zones, but they made all the difference. Five years later, Mergify isn’t just a successful SaaS company because we built great tech — it’s successful because we solved real problems for real people. (For a different angle on the same lesson, read &lt;a href=&quot;https://julien.danjou.info/blog/tech-is-the-easy-part&quot;&gt;Tech Is the Easy Part&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;And that’s the real lesson for any engineer founder: focus on the problem, not the tech, and you’ll go far.&lt;/p&gt;
</content:encoded></item><item><title>There&apos;s (almost) no GitLab</title><link>https://julien.danjou.info/blog/theres-almost-no-gitlab/</link><guid isPermaLink="true">https://julien.danjou.info/blog/theres-almost-no-gitlab/</guid><description>A word on a French bias.</description><pubDate>Tue, 29 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Do you guys support GitLab? Is there any way this can work with GitLab? Does this support merge requests?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;No, we don’t.&lt;/p&gt;
&lt;p&gt;As I spent hours screening software engineer candidates those last weeks, I repeatedly answered the same question: where’s Mergify support for GitLab?&lt;/p&gt;
&lt;p&gt;To put things into perspective, we mostly hire in the French market, and I think it deserves some context.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/029f09ae-ee03-4204-bc15-24a420f099f2_1376x864.webp&quot; alt=&quot;Illustration of the French bias toward self-hosted GitLab over GitHub&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Culture Bias&lt;/h2&gt;
&lt;p&gt;I’ve known software engineers for more than 20 years in the country of the baguette, and something is pretty clear. We have amazing engineers, but they suck at understanding what ROI means. Most people have no conception of the value of time, and for most average French engineers, it’s OK to spend time on anything as long as it avoids spending money (or requesting a budget).&lt;/p&gt;
&lt;p&gt;It’s not even a frugality thing; it really is just the inability to compute a basic return on investment and put a price on an hour of work.&lt;/p&gt;
&lt;p&gt;In the context of software forges, that means something: French companies, from startups to scaleups, are heavily biased towards deploying GitLab Community Edition because it’s free. They would do this over a cheap hosting bare metal server. You can find such hosting for around $20/month.&lt;/p&gt;
&lt;p&gt;At this price, the average French software engineer will not buy a GitHub license. They’d call that a rip-off.&lt;/p&gt;
&lt;p&gt;I’ve heard it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/670182a1-7e8a-4579-8241-7a869b24e9e1_1375x720.jpeg&quot; alt=&quot;Fire at OVH datacenter in Strasbourg, March 2021&quot; /&gt;
&lt;em&gt;Fire at OVH datacenter in Strasbourg, March 2021&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If you don’t factor in the time it takes to spin up and maintain the GitLab instance or the impact of having &lt;a href=&quot;https://www.reuters.com/article/world/millions-of-websites-offline-after-fire-at-french-cloud-services-firm-idUSKBN2B20NT/&quot;&gt;your server on fire&lt;/a&gt;, then, indeed, a price of $20/month is unbeatable.&lt;/p&gt;
&lt;p&gt;I remember asking one young French engineer in a startup about their GitLab instance and how they would maintain it and manage its security compliance. The answer was straightforward:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We just run `apt upgrade` every night so we’re sure we’ve every security deployment installed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;YMMV.&lt;/p&gt;
&lt;h2&gt;The Market Share&lt;/h2&gt;
&lt;p&gt;In April 2024, the Mergify team spent a few days at Devoxx France, the country&apos;s largest developer conference with nearly 5,000 attendees. We talked to dozens of engineers, and roughly 50% of them were using GitLab at work. Some large teams were moving away from GitLab to GitHub, but for a large majority, we were weirdos for not supporting GitLab. Their view of the market share is biased toward the French market, where GitLab might indeed have a large usage, which might not tend to generate large revenue, though. Remember that the Community Edition of GitLab is &lt;em&gt;free&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/ff991deb-3cd5-484c-be03-511a6ddf6833_3079x1800.webp&quot; alt=&quot;Mergify team at their booth at Devoxx France 2024&quot; /&gt;
&lt;em&gt;Mergify’s team at Devoxx France 2024&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If we compare &lt;a href=&quot;https://telanganatoday.com/githubs-annual-revenue-run-rate-hits-2-billion-driven-by-copilot-nadella&quot;&gt;GitHub’s $2 billion revenue&lt;/a&gt; to &lt;a href=&quot;https://about.gitlab.com/press/releases/2024-03-04-gitlab-reports-fourth-quarter-and-full-fiscal-year-2024-financial-results/&quot;&gt;GitLab’s $579 million revenue&lt;/a&gt; for 2024, this is a 1:4 ratio, which is already pretty huge. Sure, revenue is not usage, but considering that GitHub has Microsoft behind it and &lt;a href=&quot;https://www.spiceworks.com/tech/tech-general/news/gitlab-explores-sale-datadog-google-potential-buyers/&quot;&gt;GitLab is reportedly looking for a buyer&lt;/a&gt;, the future looks way brighter for GitHub — something I explored in more depth in &lt;a href=&quot;https://julien.danjou.info/blog/is-github-the-future-or-becoming&quot;&gt;Is GitHub the Future?&lt;/a&gt;. And I&apos;m not even talking about the fact that the vast majority of open-source projects use GitHub.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/fc24de53-43e4-46af-8e71-c3047d4d4bae_576x811.png&quot; alt=&quot;Screenshot of a LinkedIn comment misunderstanding GitHub vs GitLab market dynamics&quot; /&gt;
&lt;em&gt;No Gauthier, I don’t think that this is how it works.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;(&lt;a href=&quot;https://www.linkedin.com/feed/update/urn:li:activity:7254503750344622080/?commentUrn=urn%3Ali%3Acomment%3A(activity%3A7254503750344622080%2C7254531496810541056)&amp;amp;dashCommentUrn=urn%3Ali%3Afsd_comment%3A(7254531496810541056%2Curn%3Ali%3Aactivity%3A7254503750344622080)&amp;amp;dashReplyUrn=urn%3Ali%3Afsd_comment%3A(7255100415971659776%2Curn%3Ali%3Aactivity%3A7254503750344622080)&amp;amp;replyUrn=urn%3Ali%3Acomment%3A(activity%3A7254503750344622080%2C7255100415971659776)&quot;&gt;LinkedIn post&lt;/a&gt;)&lt;/p&gt;
&lt;h2&gt;Innovation&lt;/h2&gt;
&lt;p&gt;Now that I’ve set the scenery, I feel it’s safe to answer about GitLab support for Mergify. The main reasons why Mergify’s Merge Queue is not looking for GitLab support anytime soon are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Innovation happens on GitHub nowadays. Ten years ago, GitHub was behind on certain topics (hello CI), but they are now way ahead of the competition. GitHub is the place where most open-source is built and where you need to be if you’re building new products;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Most teams using GitLab CE have no intention to buy any software;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The market share of GitLab is small and probably shrinking.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I’ve nothing against GitLab, and their software might be pretty good. All we know is that they’re outsiders, while a lot of the tech market in Europe seems to think that betting on GitLab is the best go-to-market strategy.&lt;/p&gt;
&lt;p&gt;I beg to differ.&lt;/p&gt;
</content:encoded></item><item><title>What&apos;s going on with Dependabot?</title><link>https://julien.danjou.info/blog/whats-going-on-with-dependabot/</link><guid isPermaLink="true">https://julien.danjou.info/blog/whats-going-on-with-dependabot/</guid><description>We&apos;re moving away from it and I&apos;m not sure why it started to suck.</description><pubDate>Tue, 15 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I loved Dependabot. I’ve used it since Grey Baker started it in 2017. I’ve seen it grow from a one-person shop to being acquired by GitHub in 2019. It’s been a fantastic tool that created more than 5,000 pull requests on Mergify repositories. I remember the excitement of finally having a tool that would bring all the new fancy features and bug fixes of my dependencies to my project in a snap.&lt;/p&gt;
&lt;p&gt;Dependabot allowed us to fix a lot of security updates introduced by dependencies, and to be aware of anything new being released in the libraries we use.&lt;/p&gt;
&lt;p&gt;But today, we kicked Dependabot out. Dependabot let us down.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/0ee45b61-3086-492a-8caa-6348ae3405ad_2000x1256.jpeg&quot; alt=&quot;Illustration of frustration with Dependabot&apos;s declining reliability&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Security You Said?&lt;/h2&gt;
&lt;p&gt;The move to acquire Dependabot from GitHub was smart. It was one of the key features that started their security roadmap with the acquisition of Semmle and their CodeQL engine the same year. GitHub has become all about security over the last few years, which makes sense considering the cybersecurity segment&apos;s hyper-growth over the last and upcoming years.&lt;/p&gt;
&lt;p&gt;However, as Dependabot matured under GitHub, cracks started to show in what was once a flawless experience. The tool that was supposed to streamline security and updates became a source of frustration.&lt;/p&gt;
&lt;p&gt;It has major design flaws that GitHub does not seem to care about.&lt;/p&gt;
&lt;p&gt;First, Dependabot can fail silently. That happened to us multiple times a year when Dependabot would just stop working and create pull requests to update our packages. You’d think that debugging such an issue would be possible by going into the Dependabot tab of your repository, but no. The log for this is actually hidden in &lt;em&gt;Insight → Dependency graph → Dependabot.&lt;/em&gt; A strange and unintuitive location for such crucial information.&lt;/p&gt;
&lt;p&gt;Once you find your log, you can then read it and debug it yourself.&lt;/p&gt;
&lt;p&gt;That’s a major problem because there’s nothing warning you that Dependabot is broken. We are used to updating our packages regularly, so we’d know, but there’s nothing preventing your dependencies and security updates from getting stale for months without you noticing. Terrible experience.&lt;/p&gt;
&lt;h2&gt;Always Lagging Behind&lt;/h2&gt;
&lt;p&gt;We’re a Python shop. We leverage &lt;em&gt;poetry&lt;/em&gt; to manage our dependencies, and we use the latest Python version in our containers.&lt;/p&gt;
&lt;p&gt;As a Python shop, staying on the latest version helps us ensure security, performance, and compatibility. So we update it as soon as we can, usually a few days after it’s released.&lt;/p&gt;
&lt;p&gt;And then Dependabot is broken.&lt;/p&gt;
&lt;p&gt;And you have to wait weeks for GitHub to fix the problem.&lt;/p&gt;
&lt;p&gt;The last few times, we had to update ourselves Dependabot itself, &lt;a href=&quot;https://github.com/dependabot/dependabot-core/pull/10470&quot;&gt;as shown here&lt;/a&gt; or even &lt;a href=&quot;https://github.com/dependabot/dependabot-core/pull/10622&quot;&gt;here&lt;/a&gt;. We’re basically doing GitHub’s job for free, maintaining the Dependabot database ourselves for all their customers.&lt;/p&gt;
&lt;p&gt;We contacted GitHub support about this already, and they did not care at all. Their laconic answer was, “Wait for it to be updated.”&lt;/p&gt;
&lt;p&gt;Sure, thank you. We’re the ones doing the updates.&lt;/p&gt;
&lt;p&gt;I get it—maybe Fortune 500 companies don’t care about the latest Python micro releases. But for startups like ours? It’s a big deal.&lt;/p&gt;
&lt;p&gt;So today, we got rid of Dependabot and replaced it with &lt;a href=&quot;https://docs.renovatebot.com/&quot;&gt;Renovate&lt;/a&gt;. It seems better maintained and supports a larger package ecosystem than Dependabot. So far, it has simplified our workflow and is not broken on a simple Python micro update. 🤞&lt;/p&gt;
&lt;p&gt;We&apos;re also adding support for Renovate in &lt;a href=&quot;https://mergify.com&quot;&gt;Mergify&lt;/a&gt; Merge Protections, as we have done for Dependabot in the past. That will ensure you can write advanced rules for &lt;a href=&quot;https://julien.danjou.info/blog/automating-github-workflows&quot;&gt;automating your GitHub workflows&lt;/a&gt;, including automatically merging your dependency update. 🦾 Let me know if you’re interested in trying it out!&lt;/p&gt;
</content:encoded></item><item><title>Why Stock Options are Terrible for Employees</title><link>https://julien.danjou.info/blog/why-stock-options-are-terrible-for/</link><guid isPermaLink="true">https://julien.danjou.info/blog/why-stock-options-are-terrible-for/</guid><description>Not everyone&apos;s ready for it.</description><pubDate>Tue, 08 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Imagine you’re joining a startup, excited by the potential to share in its future success. You’ve just been offered stock options. What do they actually mean for you?&lt;/p&gt;
&lt;p&gt;You’re not sure if you need to negotiate these terms, or even what they imply. Once you’re in, someone mentions them during a coffee break, and you’re confused all over again. Whatever—you decide to see how it plays out. Maybe you’ll get filthy rich without understanding why. Maybe you’ll get screwed over. Who knows?&lt;/p&gt;
&lt;p&gt;As a tech employee, tech entrepreneur, and investor, I’ve had my share of discussions with fellows about stock options over the last decade.&lt;/p&gt;
&lt;p&gt;Stock options are a great leverage for founders to share future value with their (early) employees. The intention behind it is noble, but using them has terrible drawbacks.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/70ad1f91-a8b1-4c7d-8893-f17abe53ef96_2000x1121.jpeg&quot; alt=&quot;Illustration of stock options as employee compensation&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Understanding What You Get&lt;/h2&gt;
&lt;p&gt;When I joined &lt;a href=&quot;https://julien.danjou.info/blog/making-the-jump&quot;&gt;Datadog&lt;/a&gt; a few years ago, they offered different packages that you had to choose from:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Option 1: a small salary, a large number of stock options;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Option 2: a medium salary, a medium number of stock options;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Option 3: a large salary and a small number of stock options.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I believe many tech companies use the same template.&lt;/p&gt;
&lt;p&gt;A few months after I joined, the company IPO’ed. I went to a coffee break and chatted with one of my colleagues. The recent IPO and the rising stock values sparked the conversation. They asked me, “Which option did you pick when you joined?” I replied that I was looking for a venture back then, focusing on wealth creation, so I picked option 1—the salary was enough for me to live.&lt;/p&gt;
&lt;p&gt;They replied that they now regretted picking option 3.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/a7d775cf-d226-43aa-bae7-c4e24029e4c4_959x639.jpeg&quot; alt=&quot;Illustration of employee regret over choosing salary over stock options&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I suspect that most employees were more attracted to larger salaries than stock options. I wish somebody from HR could confirm the stats about what I observed, but I doubt they would. The truth is, even HR did not understand what they were doing.&lt;/p&gt;
&lt;p&gt;When I got offered the 3 packages above, I asked what the value of the stock options was. Stock options are &lt;em&gt;options&lt;/em&gt; (no shit), meaning they offer you the right to buy shares of a company. My natural question was: what was the price of this option, what was the company&apos;s value back then, and what was the total number of outstanding prices?&lt;/p&gt;
&lt;p&gt;The recruiter’s answer was: 😳&lt;/p&gt;
&lt;p&gt;Nonetheless, I insisted, and they escalated my question to an HR director. The director ended up reaching out to the US side of the company. They finally showed me a spreadsheet with the number I was asking, easing my final decision.&lt;/p&gt;
&lt;p&gt;The lesson? Always ask for clarity on the value of your options before deciding. If even the recruiter is unsure, push until you understand what you’re getting.&lt;/p&gt;
&lt;h2&gt;Not Everyone is an Investor&lt;/h2&gt;
&lt;p&gt;If you ask people what investing is all about, they’ll reply with “finance and maths.” That’s true, but it’s only one part of the equation. Investing is also a very &lt;strong&gt;psychological&lt;/strong&gt; discipline; not everyone’s ready for that.&lt;/p&gt;
&lt;p&gt;When I was working at Red Hat, the company used to distribute RSU (&lt;em&gt;Restricted Stock Units&lt;/em&gt;) to its employees. RSUs are basically free stocks. You’d get a bunch of those every year and could keep them, therefore becoming a shareholder, or sell them on the market and get cash in exchange.&lt;/p&gt;
&lt;p&gt;As the company was on a growth trajectory back then, the stock would keep climbing every month or so.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/76c9d288-c8fa-4b29-a64d-ae1f35144170_730x550.png&quot; alt=&quot;Red Hat stock price chart showing growth before the IBM acquisition in 2019&quot; /&gt;
&lt;em&gt;Red Hat stock history before being bought by IBM in 2019.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Most people I worked with were young engineers and not investors. They had no clue why the stock would fluctuate.&lt;/p&gt;
&lt;p&gt;One day, I was chatting with a colleague over a beer, and they seemed worried. I asked what was wrong, and they explained that they had lost thousands of dollars over the last month because Red Hat stocks had plunged. They had received RSUs over the last years and were so happy with their growth that they never sold them and didn’t think they could crash. Now, they were upset and did not know what to do.&lt;/p&gt;
&lt;p&gt;Of course, they did not know what to do.&lt;/p&gt;
&lt;p&gt;They had no way to know if the stock was overvalued or undervalued.&lt;/p&gt;
&lt;p&gt;They had no idea if the market would continue its bull run or if a bear market was upcoming.&lt;/p&gt;
&lt;p&gt;They were no investors. They had no plan, and the uncertainty left them feeling lost.&lt;/p&gt;
&lt;h2&gt;How to Approach Stock Options as an Employee&lt;/h2&gt;
&lt;p&gt;Owning stock options (or RSUs) means one thing: you are becoming an investor. It means that you now own (potentially, in the case of options) a part of a company with a &lt;em&gt;market value&lt;/em&gt; and an &lt;em&gt;intrinsic value&lt;/em&gt;. If you cannot assess the value or form an opinion on your company’s stock, you probably shouldn’t be a shareholder.&lt;/p&gt;
&lt;p&gt;I know. It’s not your fault. You didn’t ask for it, but this is what happens if you stick to your stock. You are transformed.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/89f84310-81fd-4016-9338-0a082c5f1569_696x239.png&quot; alt=&quot;Airbnb stock price chart illustrating the dilemma of when to sell&quot; /&gt;
&lt;em&gt;As an employee, should you keep or sell your Airbnb Inc. stock? When do you sell? Why?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;→ Once you vest your RSUs, if don’t sell them immediately in exchange for cash, you’re effectively becoming an investor.&lt;/p&gt;
&lt;p&gt;→ When a liquidity event occurs, and you choose not to exercise your stock options, you are deciding to become an investor.&lt;/p&gt;
&lt;p&gt;It’s great to be an investor, for sure. But that comes with many questions: what’s my allocation policy? What’s my exit strategy? What is my risk tolerance? What is my investment horizon? What are the tax implications? How well do I understand the business model? Do I trust the leadership team? What’s my plan if things go wrong? How does this investment fit into my overall financial plan?&lt;/p&gt;
&lt;p&gt;Investing isn’t just about understanding finances; you also need to handle fear, greed, and uncertainty. &lt;strong&gt;Most people aren’t ready for the psychological strain of watching their savings plummet overnight.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/e3d5afa6-183c-4fe3-9f2c-6701f17c779d_690x420.png&quot; alt=&quot;CrowdStrike stock chart showing a 50% drop during the 2024 incident&quot; /&gt;
&lt;em&gt;CrowdStrike shares lost 50% of their value during the incident in 2024.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Stock options can be a powerful motivator, but they aren’t for everyone. Next time you’re offered stock options, think like an investor—ask the tough questions, seek clarity, and ensure you’re prepared for both the financial and emotional aspects of investing.&lt;/p&gt;
&lt;p&gt;If you then plan to stick to your stock, decide whether you truly want to become an investor and learn the trade. If you don’t, it’s fine; just get your money and enjoy it. ✌️&lt;/p&gt;
</content:encoded></item><item><title>Why You Need Product Engineers</title><link>https://julien.danjou.info/blog/why-you-need-product-engineers/</link><guid isPermaLink="true">https://julien.danjou.info/blog/why-you-need-product-engineers/</guid><description>And not software engineers</description><pubDate>Tue, 01 Oct 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A couple of weeks ago, I attended our quarterly MAHOS (&lt;em&gt;Mergify All Hands On-Site&lt;/em&gt;)—an event where we gather the whole &lt;a href=&quot;https://mergify.com&quot;&gt;Mergify&lt;/a&gt; team together for a week—and gave a small speech about &lt;em&gt;product engineers&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/7feac5ff-7567-46db-b3e8-361202fe8ea2_3799x2622.jpeg&quot; alt=&quot;Mergify team group photo, September 2024&quot; /&gt;
&lt;em&gt;Mergify’s team, Sept. 2024&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I was unsure if I coined the term &lt;em&gt;product engineer&lt;/em&gt; at that time or if it already existed. After Googling, I found that I was not the only one who realized that we no longer need &lt;em&gt;software engineers&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;When we started our venture a few years ago and decided to hire engineers, we naturally looked for software engineers. We found great engineers. They learned a ton of stuff working with us over the last couple of years and became very efficient at producing code. Awesome. I wrote last month about how to become &lt;a href=&quot;https://julien.danjou.info/p/how-to-be-a-great-software-engineer&quot;&gt;a great software engineer&lt;/a&gt;, and while I hold to this, the next step in your career, if you want to work in a product-oriented startup, is to become a &lt;em&gt;product engineer&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;What is a Product Engineer?&lt;/h2&gt;
&lt;p&gt;Is that just a software engineer building a product? Yes! But that is not what a software engineer does by default. Let me tell you an anecdote.&lt;/p&gt;
&lt;p&gt;Last month, with my product owner hat on, I wrote a user story explaining one of the changes we needed to make in Mergify: feature X is enabled by default in the product, which is annoying because most users do need it. We need to 1. allow the users to enable or disable it and 2. make it disabled by default.&lt;/p&gt;
&lt;p&gt;One of our software engineers picks the ticket and implements a solution. Here’s what they do:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The feature X can be enabled or disabled;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The feature X is disabled by default;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;An error message warns the user constantly that feature X is currently disabled and that they need to enable it to have it work. There’s a giant red banner to warn users that feature X is disabled—until the user has enabled the feature.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When I see that, I’m really confused. The code is great, and the ticket is indeed implemented, but the last part is terrible from a user experience perspective. It forces the user to enable feature X to get rid of the warning, meaning we get back to a point where users have to enable feature X by default, even if they don’t need it, just because they’re confused.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/0432e3c6-7445-494c-b333-84b6d35c3e2e_519x167.png&quot; alt=&quot;Example of bad product design with a warning banner forcing users to enable a feature&quot; /&gt;
&lt;em&gt;Bad product design.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That is great &lt;em&gt;software engineering&lt;/em&gt; work but terrible &lt;em&gt;product engineering&lt;/em&gt; work. In no case did the engineer put themself in the user&apos;s shoes or try to understand why we needed that change.&lt;/p&gt;
&lt;p&gt;This is where a &lt;em&gt;product engineer&lt;/em&gt; must shine. They need to understand the value and the reason behind the code they write, taking into account the product, its roadmap, its priorities, etc. It requires the ability to do trade-offs by being pragmatic. They need to be obsessed with the customer and understand their problem. In a startup, you need to ship fast, meaning, again, doing trade-offs and being efficient and practical. They need to be detail-oriented, have a sense of ownership, and be on the look to create terrific experiences.&lt;/p&gt;
&lt;p&gt;Writing software has never been so easy. With AI on the rise, writing actual code will have less and less value.&lt;/p&gt;
&lt;p&gt;The core value of building software is going to be whatever AI is not yet able to do, which is &lt;em&gt;&lt;strong&gt;empathy&lt;/strong&gt;&lt;/em&gt;—connecting with and learning from other human beings’ needs.&lt;/p&gt;
&lt;h2&gt;How to Transform Engineers in Product Gurus&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/98343348-3763-4d8c-9bfd-68b936fc004c_2160x1215.png&quot; alt=&quot;Indeed job listing for product engineer showing misguided advice&quot; /&gt;
&lt;em&gt;Do not follow Indeed’s advice, for sure.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Based on the description I wrote above, we implemented some changes and made good progress overall. The improvements we made were due to simple changes we made to the organization.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Connect engineers with customers.&lt;/strong&gt; Doing support directly with customers, joining a demo call, spending time in a booth during an event, and talking to prospects. All those activities where engineering can interact with prospects and customers are very valuable;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Explain the why, not the how.&lt;/strong&gt; As a product owner, you must explain why changes are being made and not how they should be made. The more context you feed into your user stories, the easier for an engineer is to make the right decision when building a feature or fixing a problem. It’s especially important when, as a product manager, you have a technical background and you could be tempted to dictate a solution.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;There are many software engineers out there, but not many product engineers. We’ll be on the lookout for that when hiring in the future. And if you want to join a product-oriented startup in the future, make sure you change your mindset to not just writing code. 😉&lt;/p&gt;
</content:encoded></item><item><title>How To Test Your API Integration</title><link>https://julien.danjou.info/blog/how-to-test-with-an-api/</link><guid isPermaLink="true">https://julien.danjou.info/blog/how-to-test-with-an-api/</guid><description>The Three Rules That Should Govern Your Testing</description><pubDate>Tue, 24 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As I was publishing last week&apos;s post on whether &lt;a href=&quot;https://julien.danjou.info/p/is-github-the-future-or-becoming&quot;&gt;GitHub is becoming obsolete or the future of development platforms&lt;/a&gt;, they decided to trigger &lt;a href=&quot;https://blog.mergify.com/post-mortem-of-incident-2024-09-17/&quot;&gt;a two-hour interruption on Mergify in retaliation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Just kidding. I am sure they did not do that on purpose.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/0f8eda71-7106-45e0-8d33-e0530cd77668_1536x720.jpeg&quot; alt=&quot;Illustration of API integration testing challenges&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.mergify.com/post-mortem-of-incident-2024-09-17/&quot;&gt;Read my post-mortem&lt;/a&gt; if you want the whole story. The summary is that they broke their API for several hours until people started to complain, and they finally rolled back their change. Bringing down our service in the meantime.&lt;/p&gt;
&lt;p&gt;That event forces me to talk about APIs this week.&lt;/p&gt;
&lt;h2&gt;API Definitions Are Just Definitions&lt;/h2&gt;
&lt;p&gt;I won’t go into the definition of an API per se; it’d be boring. You can Google it if you need to.&lt;/p&gt;
&lt;p&gt;The real question is what &lt;em&gt;having&lt;/em&gt; an API &lt;em&gt;means&lt;/em&gt;. Offering an API to your users means authorizing them to interact with your service. This implies many rules, such as the data model of your API, the behavior of your API, the rules of usage, etc. Some can be encoded in a computer-readable machine; others cannot. Engineers like to talk about contracts, and I think it’s an almost good analogy.&lt;/p&gt;
&lt;p&gt;To describe this contract, you need multiple specifications.&lt;/p&gt;
&lt;p&gt;Developers have been ecstatic over &lt;a href=&quot;https://swagger.io/specification/&quot;&gt;OpenAPI&lt;/a&gt; over the last decade as a go-to media for describing their API. I want here to emphasize how little this documents your API. It illustrates the data model used but does not encode much of the behavior the system might exhibit.&lt;/p&gt;
&lt;p&gt;Hey, I can confirm that GitHub did not break its OpenAPI schema when it broke its API last week. Formidable.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/8d49cdca-7d99-4c42-a00f-c772ceea9087_2500x714.svg&quot; alt=&quot;Diagram showing the gap between OpenAPI schema and actual API behavior&quot; /&gt;&lt;/p&gt;
&lt;p&gt;However, based on the assumption that OpenAPI is enough, many engineers mock their API consumption based on that part of the contract and think they’re done.&lt;/p&gt;
&lt;p&gt;In that situation, the minimum you should do is validate that your mocking follows the OpenAPI schema you’re using. Even that is not enough because sometimes the schema changes—and sometimes it’s just not respected.&lt;/p&gt;
&lt;p&gt;Let’s take GitHub again as an example. Their API is so legacy that the &lt;a href=&quot;https://json-schema.org/blog/posts/github-case-study&quot;&gt;JSON schemas were crafted manually&lt;/a&gt; — and there might still be, I don’t know. It’s fine; it’s better than nothing, and it’s not obvious to change a legacy API that’s been there for 15 years.&lt;/p&gt;
&lt;p&gt;We know first-hand that their system does not always respect the GitHub API JSON Schema.&lt;/p&gt;
&lt;h2&gt;APIs Have Side-effects&lt;/h2&gt;
&lt;p&gt;Again, this approach is entirely based on the data model and is insufficient and of little value.&lt;/p&gt;
&lt;p&gt;Most of an API&apos;s value is in the behavior it triggers. Unless your API is a basic CRUD and does storage only, it will have side effects that might or might not be visible through the API.&lt;/p&gt;
&lt;p&gt;For example, creating an asynchronous job on any REST API will return nothing except a unique identifier, which can be used later to identify the work. You might receive the data via a webhook or have to poll the API to get the job’s status. This kind of behavior cannot be documented in OpenAPI as it’s not part of the data model; there’s nothing to tell you to expect a webhook.&lt;/p&gt;
&lt;h2&gt;API Invisible Parts&lt;/h2&gt;
&lt;p&gt;Now, let’s discuss all the invisible parts of running an API. There are many. The first that come to mind are RBAC, quota, and rate limits. Most APIs have to implement those items, and they also have a direct impact on the API behavior and access.&lt;/p&gt;
&lt;p&gt;Those features will massively impact the quality and quantity of API use. Again, they are pretty hard to test in a black box. There’s no way you can easily mock a full RBAC implementation or real-life rate limits.&lt;/p&gt;
&lt;h2&gt;Testing the Hard Way&lt;/h2&gt;
&lt;p&gt;Having consumed many different APIs for the last five years on Mergify, and especially GitHub’s one that we know by heart, gave us a few ideas on how you can or cannot test.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rule number one: do not mock. Record your tests.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We leverage &lt;a href=&quot;https://vcrpy.readthedocs.io/en/latest/usage.html&quot;&gt;vcrpy&lt;/a&gt; in Python to do that: the idea is to run your test in a &lt;em&gt;record mode&lt;/em&gt; where real HTTP requests are done against a service. Once the recording is done, you can replay the test when running it locally or in the CI.&lt;/p&gt;
&lt;p&gt;If any of your code tries to make a different HTTP call, the test will fail, and you will have to re-record it. This ensures that no change is made to the application without being noticed.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/ca786a70-ec3e-43f4-93dd-86c8ab02b27a_942x111.png&quot; alt=&quot;Screenshot of vcrpy test recording detecting a changed HTTP call&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now, that does prevent your application from being broken, but that does not prevent the API from breaking your app. The only way to do this is to regularly re-record all the tests and see if they break.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So, rule number two: re-record your tests regularly — every day if possible.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For example, we have a test that plays with GitHub pull request labels. When re-recording a test a few months ago, we noticed that if it failed. It turned out that GitHub changed its API to become case-sensitive overnight (that was not in the OpenAPI schema!).&lt;/p&gt;
&lt;p&gt;In that case, we preferred to ask GitHub to fix their API rather than fix our code, but hey, &lt;em&gt;your mileage may vary&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rule number three: be ready to fix the code.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;No amount of testing will cover all the edge cases. For example, requests quota or rate limit might be hit in real scenarios but not in testing, meaning you’ll have to handle those specific cases without being able to test. It’s fine — you can actually mock part of the responses here.&lt;/p&gt;
&lt;p&gt;For this, we leverage &lt;a href=&quot;https://sentry.io&quot;&gt;Sentry&lt;/a&gt; to obtain evidence of the problem, replicate it in a test, and fix it. No amount of testing can fix all scenarios, so having a way to &lt;em&gt;hotfix&lt;/em&gt; your code is a must.&lt;/p&gt;
&lt;p&gt;In the end, mixing API test recording for safety and error tracking for fast action is the best combination we’ve seen for dealing with external systems.&lt;/p&gt;
&lt;p&gt;If we map those rules to last week&apos;s incident, rule number three helped to fix the issue quickly, while rule number one would have technically caught it, and rule number two would have done so in less than 24 hours. Even if it turned out in our case that reality kicked in before testing.&lt;/p&gt;
&lt;p&gt;So use that. And retry mechanisms.&lt;/p&gt;
&lt;p&gt;I guess that’ll be for another post.&lt;/p&gt;
</content:encoded></item><item><title>Is GitHub the Future or Becoming Obsolete?</title><link>https://julien.danjou.info/blog/is-github-the-future-or-becoming/</link><guid isPermaLink="true">https://julien.danjou.info/blog/is-github-the-future-or-becoming/</guid><description>Is GitHub the Future or Becoming Obsolete?</description><pubDate>Tue, 17 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Over the last few months, I stumbled upon a few articles on GitHub&apos;s history and future. I find those quite interesting. &lt;a href=&quot;https://graphite.dev/blog/github-monopoly-on-code-hosting?ref=blog.gitbutler.com&quot;&gt;Greg Foster gives a quick history of the rise of GitHub over the years&lt;/a&gt;, while Scott Chacon, one of GitHub’s co-founder, &lt;a href=&quot;https://blog.gitbutler.com/why-github-actually-won/&quot;&gt;retraces the history of GitHub from the inside&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The story is great, and I’ll leave it to you to read those posts if you want to learn more. I was alive at that time; I saw and lived it all. I started using Git in 2005, and I’m the 2644th of the 100 million GitHub users—I joined GitHub in 2008 when it was still in beta.&lt;/p&gt;
&lt;p&gt;It’s definitely true that Git won, and especially GitHub. I’ll probably have to write about GitLab at some point (&lt;a href=&quot;https://julien.danjou.info/blog/theres-almost-no-gitlab&quot;&gt;I eventually did&lt;/a&gt;) — but you won’t read any ranting here today. As Scott Chacon writes, GitHub had a taste and perfect timing and soon gained traction from the open-source community.&lt;/p&gt;
&lt;p&gt;Ok, great, so GitHub wins. Where do we go now?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/2a67ca8f-23bc-40da-bf28-f18b10b0be60_2000x1000.jpeg&quot; alt=&quot;Illustration of GitHub&apos;s dominance in code hosting&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Microsoft&lt;/h2&gt;
&lt;p&gt;I believe that GitHub&apos;s growth was already on track before Microsoft bought it in 2018, but that move still changed everything. At that time, GitHub still lacked major features, such as a CI/CD system, and had a hard time being compared to GitLab. The following year that changed, and GitHub Actions started (thanks, Azure) and shook everything up.&lt;/p&gt;
&lt;p&gt;If you read opinions about GitHub vs. GitLab, CodeCommit, Azure DevOps, or any other platform, you’ll mostly see engineers comparing user-visible features. And sure, this has value, and that might be your main criteria to pick one or the other if you’re a small team with full power over their choice.&lt;/p&gt;
&lt;p&gt;However, this is not what GitHub and Microsoft are after anymore. Take a look at the &lt;a href=&quot;https://github.com/github/roadmap&quot;&gt;roadmap&lt;/a&gt; of the last few years, and you’ll see a pattern: &lt;em&gt;enterprise&lt;/em&gt;. Pushing code, creating pull requests, and any part of software engineers&apos; day-to-day activities have been covered for the last 15 years.&lt;/p&gt;
&lt;p&gt;They designed it, the industry adopted it, and GitHub has nothing in its roadmap to change that paradigm. They’re building on the momentum they raised.&lt;/p&gt;
&lt;p&gt;Security, Copilot (AI), and compliance are items that the largest corporation needs to embrace a platform such as GitHub. This is only the beginning: this year, the GitHub sales organization underwent a reorganization to look more like Microsoft&apos;s sales organization. I suspect GitHub is now able to leverage even more Microsoft resources to push its platform to large corporations—which definitely makes sense. The link between GitHub and Azure is tightening, both technically and commercially.&lt;/p&gt;
&lt;p&gt;For the best and the worst.&lt;/p&gt;
&lt;h2&gt;Relevance&lt;/h2&gt;
&lt;p&gt;How is GitHub still relevant if it does not innovate on developer workflow? &lt;a href=&quot;https://www.mistys-internet.website/blog/blog/2024/07/12/github-is-starting-to-feel-like-legacy-software/&quot;&gt;Is it becoming legacy software&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;There is certainly a disjunction between what developers and corporations expect, and at this point, if you look at the ratio between features, compliance, and price, there&apos;s no better alternative than GitHub. I don’t think this is going to change anytime soon.&lt;/p&gt;
&lt;p&gt;For open-source projects, there might be alternatives, but if you’re pragmatic (and lazy), GitHub is the pick. There have been a number of projects trying to escape GitHub over the years, such as when Microsoft acquired it. The latter is still seen as an enemy of open source by some folks (I suspect this is PTSD caused by the &lt;a href=&quot;https://www.zdnet.com/article/ballmer-i-may-have-called-linux-a-cancer-but-now-i-love-it/&quot;&gt;Balmer&lt;/a&gt; era). More recently, another exodus was triggered by the announcement of Copilot and the fear that the training was done on free software. However, at this stage, it’s like emptying an ocean with a spoon, and the impact does not compensate for larger projects moving to GitHub (e.g., &lt;a href=&quot;https://peps.python.org/pep-0512/&quot;&gt;Python&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/3051d896-9814-41f5-9866-20a74692b5d0_1200x630.jpeg&quot; alt=&quot;Steve Ballmer portrait&quot; /&gt;
&lt;em&gt;Steve Ballmer&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;On the other hand, GitHub is attracting more competitors. It has grown to a point where many startups want to disrupt it: one can look at &lt;a href=&quot;https://radicle.xyz/&quot;&gt;Radicle&lt;/a&gt; and its decentralized approach, &lt;a href=&quot;https://pierre.co/&quot;&gt;Pierre&lt;/a&gt; and its modern design, &lt;a href=&quot;https://www.diversion.dev/&quot;&gt;Diversion&lt;/a&gt; with its game-centric approach, or &lt;a href=&quot;https://www.palmier.io/&quot;&gt;Palmier,&lt;/a&gt; who’s building a new kind of repository.&lt;/p&gt;
&lt;p&gt;They might succeed, but the road is going to be long to get massive adoption — and migration.&lt;/p&gt;
&lt;p&gt;There’s nothing replacing GitHub in the short term. We better deal with it.&lt;/p&gt;
</content:encoded></item><item><title>Staying Competitive</title><link>https://julien.danjou.info/blog/staying-competitive/</link><guid isPermaLink="true">https://julien.danjou.info/blog/staying-competitive/</guid><description>How to fight (back) big corporations</description><pubDate>Tue, 10 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Starting a company is always a challenge. You start small, and you might have to fight competitors that are way larger than you. They could crush you.&lt;/p&gt;
&lt;p&gt;However, being small does not mean that you will necessarily lose against large companies.&lt;/p&gt;
&lt;p&gt;Last week, I was wandering through a forum where I regularly hang out with other SaaS founders. We share founders’ issues, anything from how to run ad campaigns to how to fire people (sigh).&lt;/p&gt;
&lt;p&gt;That day, one of the participants asked a question about staying competitive when large companies enter your market. They were facing a few challenges, the main one being that a multi-billion corporation was adding features to its product that would poach on their turf.&lt;/p&gt;
&lt;p&gt;That resonated with me. Mergify went through the same hassle when &lt;a href=&quot;https://julien.danjou.info/blog/the-challenges-of-merge-queues&quot;&gt;GitHub launched its own merge queue features&lt;/a&gt; last year.&lt;/p&gt;
&lt;p&gt;Being in this position is extremely risky. There are many horror stories out there of startups being killed by larger competitors. You’d just have to watch the latest OpenAI DevDay announcements over the last few months to see how many startups they would instantly disrupt with the announcement of a single feature (marketplace, custom GPTs, etc.).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/cf70dc33-1f44-41b7-aa8d-84ba67f62c1a_1516x416.png&quot; alt=&quot;Apple iOS 18 announcement threatening to disrupt smaller competitors&quot; /&gt;
&lt;em&gt;Is Apple going to kill a bunch of companies with iOS18?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Now, that being said, being the underdog is not necessarily a bad thing and can turn out to be a strength. When survival is at stake, the smallest dog can become the more fierce one.&lt;/p&gt;
&lt;h2&gt;80/20&lt;/h2&gt;
&lt;p&gt;In 1668, Jean de La Fontaine wrote a famous fable, &lt;em&gt;Le Lièvre et la Tortue&lt;/em&gt;. If you never heard of it, the TL;DR is: a confident hare mocks a slow tortoise and challenges her to a race. The hare, overconfident, takes a nap mid-race while the tortoise steadily continues and wins. The fable teaches the lesson that persistence and diligence can triumph over arrogance and haste.&lt;/p&gt;
&lt;p&gt;There are multiple choices to do in this situation, but my feeling is that most large companies will implement the “20% features that do 80% of the job.” It makes sense to them economically. With little effort, they can enter the market and grasp a large amount of the share using their moat, branding, marketing, and existing customers.&lt;/p&gt;
&lt;p&gt;They can leverage their vast resources and existing ecosystems to quickly dominate this space. However, they often leave gaps in niche or specialized needs because building the remaining 80% of features to cover every specific use case requires more effort and may not align with their broader goals.&lt;/p&gt;
&lt;p&gt;Unfortunately, if you were also targeting the same 20% feature, this can hurt your business. You’ll stop seeing new customers and will lose existing ones as they remove the one-too-many vendor from their list.&lt;/p&gt;
&lt;p&gt;However, there is a chance for you to stay ahead of the game. Like the hare in the tale, large companies will just take a gigantic nap once they’re done with what they expect to be enough. This is where you can shine.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/2508d33f-3137-40ba-ac04-efd71bb84318_1580x1180.png&quot; alt=&quot;Illustration of the 80/20 rule applied to feature coverage and market share&quot; /&gt;&lt;/p&gt;
&lt;p&gt;A small company that builds beyond the basic 20% and focuses on solving complex or specialized problems can appeal to customers who require more tailored solutions, thus capturing a large share of a more focused market. While large companies serve the general market, small players can dominate specialized verticals. By implementing, e.g., 40% of the features for a particular scope, you could address 97% of the market, thus appearing as an expert and leader in your area.&lt;/p&gt;
&lt;h2&gt;Vertical&lt;/h2&gt;
&lt;p&gt;Another play that I like is to target different verticals. If you design presentation software and suddenly PowerPoint enters the market, it’s going to be pretty hard to win over the long run. Microsoft will win because they’re known (the “&lt;em&gt;nobody gets fired for buying IBM&lt;/em&gt;” rule), and they can reach out to millions of customers — you can’t.&lt;/p&gt;
&lt;p&gt;However, if you pivot to becoming the presentation software specialized in building convincing, AI-generated, prospect-centered sales decks automatically, then you can win an entire part of the broader market.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/a437ebee-cfed-4dee-9528-f75711e49af2_2000x1000.jpeg&quot; alt=&quot;Illustration of targeting vertical markets to compete against large corporations&quot; /&gt;&lt;/p&gt;
&lt;p&gt;For sure, the initial market might be narrower, but by being focused, it’ll be easier to win it. As you’re winning it, you can expand into adjacent markets and grab more share of the global offering.&lt;/p&gt;
&lt;h2&gt;Strategy&lt;/h2&gt;
&lt;p&gt;For both strategies — expanding beyond the 80/20 or addressing a particular, the key to discovering which approach might suit you better is to talk to your existing customers. They are the ones having the answer to the questions “Is there a usage pattern they rely on?” or “Are they working in the same industry?”. Focusing on customer experience or specializing in integrations that large corporations won’t do is only identifiable if you speak to your users.&lt;/p&gt;
&lt;p&gt;Small companies have the agility and execution speed that large companies lack, making them the best innovators.&lt;/p&gt;
&lt;p&gt;I guess that’s my message to anyone out there being attacked by large corporations. Don’t throw the towel too soon. There might be different plays for you to continue growing and fighting back against the Goliath.&lt;/p&gt;
&lt;p&gt;In the end, it’s not just about size. It’s about understanding where you can uniquely provide value and delivering that value better and faster than anyone else.&lt;/p&gt;
</content:encoded></item><item><title>How to Be a Great Software Engineer</title><link>https://julien.danjou.info/blog/how-to-be-a-great-software-engineer/</link><guid isPermaLink="true">https://julien.danjou.info/blog/how-to-be-a-great-software-engineer/</guid><description>There is more than one way.</description><pubDate>Tue, 03 Sep 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I did not write for the last few weeks as I enjoyed taking a break. Ha! That’s probably the first point I could write about being a great software engineer: taking breaks.&lt;/p&gt;
&lt;p&gt;Nevermind.&lt;/p&gt;
&lt;p&gt;What do I know, after all? I’m not a software engineer anymore. I’m a CEO, god damn it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/f4dfeb44-ffa2-4a3f-baf2-0dcf75586f06_320x195.jpeg&quot; alt=&quot;Illustration of a CEO dispensing advice&quot; /&gt;
&lt;em&gt;Not my role model.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;At least being a CEO gives me some excuse to dispense some pieces of advice regularly. It turns out that over the last couple of years, I had to become a &lt;em&gt;manager&lt;/em&gt; of people — and many people in my team are software engineers. The only thing I knew about management so far was &lt;em&gt;being managed&lt;/em&gt;, which taught me many things, such as:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;how to manage;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;how not to manage;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;how to be managed.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I don’t want to talk about the first two points here, but I’d like to write about the latter. I regularly have to give feedback to people on my team, and they often rely on the Great Engineer Framework that I built in my mind.&lt;/p&gt;
&lt;p&gt;It’s time to write that down.&lt;/p&gt;
&lt;h3&gt;Expectations&lt;/h3&gt;
&lt;p&gt;Since I started my career as a software engineer 20 years ago, I always wondered how to improve. My initial appeal for this career was typing code on a keyboard, so I decided that the best way to become a great engineer was to be the best at technical stuff.&lt;/p&gt;
&lt;p&gt;I coded days and nights, learned everything I could, and became amazing. I Debian-packaged hundreds of software, wrote C code for a window manager, Linux and CPython, wrote &lt;a href=&quot;https://github.com/emacs-mirror/emacs/blob/master/lisp/color.el#L301&quot;&gt;CIEDE2000 color space computation functions in Lisp&lt;/a&gt;, wrote thousands of lines of Python to do crazy stuff, implemented XML binding for the X11 protocol, built a scalable time-series database based on object storage, etc; you name it. I did many tech-crazy things and thought I was a great engineer.&lt;/p&gt;
&lt;p&gt;It turns out I was only 33% good. As I grew into the tech and startup ecosystem, I started to understand whatever was around me, the industry, the business, the people. And I soon realized that this was not enough, even if I was among the best engineers you could probably find (sorry for bragging).&lt;/p&gt;
&lt;h3&gt;Aspects&lt;/h3&gt;
&lt;p&gt;After a few years, I built a mental model that I still use nowadays to give feedback to engineers in my team, based on 3 aspects that you must master to become a great engineer — like the 10x engineer they all talk about:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Tech&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Business value&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Collaboration&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Tech&lt;/h4&gt;
&lt;p&gt;I just discussed tech. You have to be &lt;em&gt;amazing&lt;/em&gt; at it, which means you have to dig &lt;em&gt;deep&lt;/em&gt; into it. As my co-founder Mehdi says, great engineers &lt;em&gt;pull the strings&lt;/em&gt;. This means that you’re not just there to paper over the problem; you’re here to understand it fully, to grasp it entirely, from top to bottom, and to fix it forever because you &lt;em&gt;understand&lt;/em&gt; it.&lt;/p&gt;
&lt;p&gt;Many junior engineers are not able to do that. They just tinker with their code until “well, it works, tests pass, whatever.” The rise of AI tooling is supporting that, and engineers working this way will have to step up their game, or they’ll disappear.&lt;/p&gt;
&lt;p&gt;It takes a large amount of time to achieve this expertise, and as common sense says, maybe 10,000 hours. This is actually a major issue for people switching to tech after another career; 10,000 hours of coding 25 hours a week (if you just do it on the job) in a typical 45-week year is more than 8 years before starting to “know what you’re talking about.” If you start at 18 years old, tinkering with computers 60 hours a week for fun, you’ll be pretty good at it by 21. I know that’s not fair, but I see this as a major roadblock for hiring tech talents coming from a career change.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/4c43b2e1-118b-4c39-9a31-4b09a1adbad1_1536x768.webp&quot; alt=&quot;Illustration of deep technical expertise and pulling the strings as an engineer&quot; /&gt;&lt;/p&gt;
&lt;p&gt;So: do tech. Don’t stop until you understand everything of what you are responsible for. I remember 15 years ago being screened by a recruiter at Google who’d ask me what happened when I typed google.com in my Web browser. Being able to explain everything, from the keyboard input to the DNS requests and TCP headers of the packet sent, to the HTTP server made me pass without a blink.&lt;/p&gt;
&lt;h4&gt;Business Value&lt;/h4&gt;
&lt;p&gt;This sounds totally stupid, and I might be slightly biased by my French experience, but there are too many engineers who do not understand &lt;em&gt;business value&lt;/em&gt;. It actually took me a few years to understand this, probably because I was only focused on tech. Let me give you a good anecdote to illustrate this.&lt;/p&gt;
&lt;p&gt;Ten years ago, I was called by a senior manager to help with a Python project in a media company. I go to the meeting and meet the manager. He comes from a famous French tech school — one where they learn the C standard library from scratch in their first year — and so do most of the engineers in his team. They’re managing hundreds of servers, and after evaluating various software to do that (Puppet, Ansible, etc) they didn’t find anything that suited 100% of their needs, so they built their own. They invested hundreds of hours in it, and now they’d need help maintaining it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/93fa670c-6f32-494d-9ef1-d9c0eac35899_1536x768.webp&quot; alt=&quot;Illustration of engineers building custom tools instead of using existing solutions&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It turns out that what they needed was probably Ansible plus a custom plugin to reach 98% of their need in a tenth of the time, but they didn’t see it that way. They just built an entire tech project, not related to their core business, from the ground up, investing hours of time. This is the kind of similar experience I talked about in my previous post, &lt;a href=&quot;https://julien.danjou.info/p/solving-build-vs-buy&quot;&gt;Solving Build vs Buy&lt;/a&gt;. I skipped that project and moved on to other things. I had no interest in maintaining a project that was not providing &lt;em&gt;core value&lt;/em&gt; to the business. That’d have been a great way to be ditch as soon as somebody smarter in the company realized the amount of wasted time that project might have been.&lt;/p&gt;
&lt;p&gt;This kind of behaviour applies everywhere. Engineers would spend hours trying to implement &lt;em&gt;perfect&lt;/em&gt; systems that will scale to millions of users. While the business might have no user — yet. Engineers would spend hours building a feature or solving a problem that would impact 0.1% of users. It’s true that engineering might not be entirely responsible for the roadmap directly, but they are responsible for the time they spend on how far they go into implementing systems and features.&lt;/p&gt;
&lt;p&gt;We live in a world where economy is the driver, which means you have to maximum throughput and minimize input. Input is your coding time, and throughput is the (extra) money the company that hires you can make with you work.&lt;/p&gt;
&lt;h4&gt;Collaboration&lt;/h4&gt;
&lt;p&gt;I could probably summarize this aspect with just:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you want to go fast, go alone. If you want to go far, go together.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/20afac45-c6a6-45f7-b50d-22529146569d_1536x768.png&quot; alt=&quot;Illustration of teamwork and collaboration as key to going far&quot; /&gt;&lt;/p&gt;
&lt;p&gt;That’s entirely true. Considering the team is a problem for many engineers who are frustrated by members of their team. It does take time to deal with people, and they are not as easy to understand as computers. However, in the long run, they are the best way to provide you with &lt;em&gt;leverage&lt;/em&gt; to achieve amazing things. Maybe another secret of 10x engineers, who knows?&lt;/p&gt;
&lt;p&gt;Therefore, you’ll need to understand the dynamics that make your teamwork. You have to make sure your work is not isolated and is not the only correct piece of code hidden in its own corner. You need to connect both your piece of software and your brain to other pieces of software and people. I know it requires a lot of effort for some people, especially because it can feel annoying and inefficient to talk or write things for other engineers to understand what you’re achieving.&lt;/p&gt;
&lt;p&gt;But until we can all Neuralink, yes, you’ll have to pause and do something that seems like a waste of time: talking to your teammates, your manager, or customers.&lt;/p&gt;
&lt;p&gt;Always remember: this is an investment. In the long run, it &lt;em&gt;will pay off.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Those three aspects are always the ones that I use to drive my feedback on performance reviews to engineers in the team. Not everyone is always 10/10 in every aspect, so it eases providing feedback and drives them to where they should improve next. They are probably not exhaustive, but they are a great way to spot great and inadequate behaviors.&lt;/p&gt;
</content:encoded></item><item><title>Launching Byte Brigade</title><link>https://julien.danjou.info/blog/launching-byte-brigade/</link><guid isPermaLink="true">https://julien.danjou.info/blog/launching-byte-brigade/</guid><description>Empowering Developer Tool Startups</description><pubDate>Tue, 20 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Today I&apos;m excited to announce the launch of &lt;a href=&quot;https://bytebrigade.fund&quot;&gt;Byte Brigade&lt;/a&gt;, a community dedicated to investing in early-stage developer tools and SaaS companies. Our mission is to support tech startups at their initial stage of development, helping them become leaders in their respective fields&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/87fced55-6f35-4249-a9e7-2e0deb34430a_2048x2048.webp&quot; alt=&quot;Byte Brigade logo&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;The Challenge&lt;/h2&gt;
&lt;p&gt;Many developers start companies to build incredible tools, but they face enormous challenges when it comes to marketing and productizing their innovations. This is particularly true in the French ecosystem, where we have brilliant engineers but often &lt;a href=&quot;https://julien.danjou.info/blog/why-french-tech-is-playing-not-to&quot;&gt;lack the cultural know-how&lt;/a&gt; to propel projects to their full potential. For example, marketing to developers is tough, and addressing the US market from France adds another layer of complexity.&lt;/p&gt;
&lt;p&gt;I&apos;ve seen this problem repeatedly in my career. For instance, I&apos;ve recently shared insights with &lt;a href=&quot;https://codspeed.io/&quot;&gt;CodSpeed&lt;/a&gt; on the differences between European and US marketing strategies, helping them understand the specific challenges they might face. This is just one example of the kind of guidance that can make a significant difference for tech startups.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/0453fed0-e13a-4c89-b768-84081f58a184_2558x579.webp&quot; alt=&quot;Byte Brigade portfolio companies and investment focus areas&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;My Journey&lt;/h2&gt;
&lt;p&gt;Having worked in open source for over 25 years and in devtools and SaaS for close to 15 years with companies like &lt;a href=&quot;https://redhat.com&quot;&gt;Red Hat&lt;/a&gt;, &lt;a href=&quot;https://datadoghq.com&quot;&gt;Datadog&lt;/a&gt;, and &lt;a href=&quot;https://mergify.com&quot;&gt;Mergify&lt;/a&gt;, I understand these challenges intimately. Over the past few years, I&apos;ve had the privilege of funding and mentoring several tech startups, helping them navigate the tricky landscape of market fit and growth.&lt;/p&gt;
&lt;p&gt;This experience has inspired me to create &lt;a href=&quot;https://bytebrigade.fund&quot;&gt;Byte Brigade&lt;/a&gt;—a collective of tech enthusiasts who can bring their unique expertise to startups aiming to grow. We are building a group of business angels interested in investing in companies that could become tomorrow&apos;s tech leaders. Byte Brigade is unique in that we are very developer-centric, focusing on solving problems that matter most to developers.&lt;/p&gt;
&lt;h2&gt;Our Approach&lt;/h2&gt;
&lt;p&gt;Our approach is simple: we invest in and guide startups, helping them transform into dev tools and SaaS powerhouses. This involves significant efforts in marketing and product development, areas where we provide invaluable guidance. With advanced technology, experienced leadership, and a growing customer base, our portfolio companies are well-positioned for significant growth. Once they refine their strategies in their initial verticals, they can expand into new sectors, leveraging their technological capabilities and market insights.&lt;/p&gt;
&lt;h2&gt;Looking Ahead&lt;/h2&gt;
&lt;p&gt;In the long term, I envision Byte Brigade growing into a community where multiple developers and business angels come together to invest in tech startups that solve problems for developers. While we have no set timeline, we plan to host events, workshops, and networking opportunities for our members and portfolio companies, fostering a collaborative environment where ideas and experiences can be shared.&lt;/p&gt;
&lt;p&gt;If you&apos;re a developer or a business angel interested in investing in companies that build solutions for developers, I invite you to join us. Your expertise and investment can help shape the future of tech innovation. On the other hand, if you&apos;re a tech company targeting developers and need funding and mentorship, we’re here to support you. Feel free to reach out to &lt;a href=&quot;mailto:hello@bytebrigade.fund&quot;&gt;hello@bytebrigade.fund&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Call to Action&lt;/h2&gt;
&lt;p&gt;Launching Byte Brigade marks an exciting new chapter in my journey to support and empower developer tool startups. I&apos;m eager to see what we can achieve together and look forward to helping many more companies grow and succeed. Join us in driving innovation and growth in the tech world.&lt;/p&gt;
&lt;p&gt;Stay tuned for more updates, and let&apos;s build the future of developer tools together.&lt;/p&gt;
&lt;p&gt;For more information or to get involved, please contact us at &lt;a href=&quot;mailto:hello@bytebrigade.fund&quot;&gt;hello@bytebrigade.fund&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Solving Build vs Buy</title><link>https://julien.danjou.info/blog/solving-build-vs-buy/</link><guid isPermaLink="true">https://julien.danjou.info/blog/solving-build-vs-buy/</guid><description>A Developer’s Dilemma</description><pubDate>Tue, 13 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Selling to developers is challenging, and the mindset of “build or buy” is one of the biggest hurdles. Over the past year at &lt;a href=&quot;https://mergify.com&quot;&gt;Mergify&lt;/a&gt;, we&apos;ve encountered countless engineers grappling with this dilemma.&lt;/p&gt;
&lt;p&gt;Developers are natural problem-solvers. When they encounter an issue, their first instinct is often to build a solution themselves. They see a problem, identify it, and immediately start imagining how they would solve it. However, there&apos;s a significant gap between having expertise in engineering and having expertise in a specific solution.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/b4c65ae0-f84e-4061-a094-35e39c1f133d_1376x864.webp&quot; alt=&quot;Illustration of the build vs buy dilemma for developers&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;The Overconfidence Trap&lt;/h2&gt;
&lt;p&gt;Many developers fall into the trap of underestimating the complexity of the problems they&apos;re trying to solve. It&apos;s akin to asking an engineer, &quot;How long to fix XYZ?&quot; and hearing, &quot;Should be done today,&quot; only for the task to stretch out over a week as unforeseen complications arise. They start working on the problem, only to discover layers of hidden challenges—refactoring needs, unexpected dependencies, and more. It all makes sense in hindsight, but it&apos;s nearly impossible to foresee these issues at the outset.&lt;/p&gt;
&lt;p&gt;This overconfidence often leads to the creation of subpar solutions. While some teams might eventually succeed, these homegrown solutions fall short of their commercial counterparts more often than not. Moreover, solving problems outside of your core business usually results in a poor return on investment. Think about it: how many companies still manage their own email servers when GSuite offers a hassle-free solution for just $6 per user per month? Your IT team is incapable of competing with such an offer.&lt;/p&gt;
&lt;p&gt;The typical customer for this comes to your demo call with a speech along those lines: “We’ve tried building a solution to this problem, but we failed because we hit too many bumps in the road; it seems you guys know how to solve it.” Teams that take this road are the easiest to win customers because they already know they can’t build.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/8f07ef4b-9de1-4f46-b001-2b3939f1c38c_1376x864.webp&quot; alt=&quot;Illustration of customers discovering the complexity they underestimated&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;The Build or Buy Mindset in Action&lt;/h2&gt;
&lt;p&gt;During our demo sessions at Mergify, we frequently encounter engineers who are initially skeptical about buying a solution. They come with a build-or-buy mindset, confident in their ability to solve the problem themselves. They&apos;re curious about why they should spend money on a product when they believe they can build it in-house. This is where it gets interesting.&lt;/p&gt;
&lt;p&gt;One of the most enjoyable aspects of these sessions is debunking their assumptions. For instance, many engineers approach our merge queue system thinking, &quot;This is just an automatic rebase, right?&quot; After a 20-minute demo, they often leave with a new appreciation for the complexities involved. &quot;Oh, okay. That sounds quite hard to do. Good job.&quot; This is where I reply with: “Well, this is why we’ve been working on this for 5 years already and have hundreds of customers.” 😉 Let me brag a bit.&lt;/p&gt;
&lt;p&gt;By walking them through the numerous edge cases and intricacies that our product handles, we can show them just how challenging the problem really is.&lt;/p&gt;
&lt;h2&gt;The Power of Demonstrating Value&lt;/h2&gt;
&lt;p&gt;This experience highlights why startup founders who are engineers can be excellent salespeople. They understand the technical mindset and can effectively communicate the value of their products. The key is to convey the value of your product in your messaging and demos.&lt;/p&gt;
&lt;p&gt;Having at least one feature that is both highly valuable and challenging to implement can make a significant impact. The more features like this you can demonstrate, the better—provided they solve real problems and aren&apos;t just complex for complexity&apos;s sake.&lt;/p&gt;
&lt;h2&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;The &quot;build or buy&quot; dilemma is a significant barrier in marketing devtools. Developers&apos; natural inclination to build solutions themselves can lead to underestimations of complexity and overconfidence. By demonstrating the intricate challenges your product solves and highlighting its value, you can shift their perspective. In the end, it&apos;s about showing that your solution is not just a convenience but a necessity for efficient and effective problem-solving. Once you&apos;ve won them over, the next challenge is &lt;a href=&quot;https://julien.danjou.info/blog/saas-pricing-is-hard&quot;&gt;getting pricing right&lt;/a&gt; — which has its own set of hard lessons.&lt;/p&gt;
</content:encoded></item><item><title>Connecting the Dots with AI</title><link>https://julien.danjou.info/blog/connecting-the-dots-with-ai/</link><guid isPermaLink="true">https://julien.danjou.info/blog/connecting-the-dots-with-ai/</guid><description>The Future of Enhanced Communication</description><pubDate>Tue, 06 Aug 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In any conversation, a lot of context can be lost between what you think, what you say, what the listener hears, and what they ultimately understand. This loss of information can lead to miscommunication and inefficiencies. How often have you found yourself confused by someone&apos;s words, asking them what they mean, only to hear, &quot;Sorry, I was thinking about this,&quot; and finally, the dots connect?&lt;/p&gt;
&lt;p&gt;This common scenario underscores AI&apos;s potential to revolutionize communication. Imagine a world where your AI assistant, enriched with context from your daily activities, bridges the gap between thoughts and understanding. This could transform the way we interact, making communication more efficient and precise.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/48fd302a-01a1-4a4b-a49d-49ff6b2d910f_303x435.png&quot; alt=&quot;Illustration of context loss in human communication&quot; /&gt;
&lt;em&gt;Communicating&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;The Role of AI in Enhancing Communication&lt;/h2&gt;
&lt;p&gt;In today&apos;s world, computers and phones are already tracking many of our communications through platforms like email, Slack, and Teams. Combining all of those platforms captures the full context of conversations — which is why people are starting to use them as sources for &lt;a href=&quot;https://research.ibm.com/blog/retrieval-augmented-generation-RAG&quot;&gt;RAG (Retrieval-Augmented Generation)&lt;/a&gt; in LLM.&lt;/p&gt;
&lt;p&gt;Technologies such as &lt;a href=&quot;https://support.microsoft.com/en-us/windows/retrace-your-steps-with-recall-aa03f8a0-a78b-4b3e-b0a1-2eb8ac48701c#:~:text=With%20Recall%2C%20you%20have%20an,takes%20snapshots%20of%20your%20screen.&quot;&gt;Microsoft Recall&lt;/a&gt; are going in that direction: recording more information to improve the AI context to make you even more able to understand your world.&lt;/p&gt;
&lt;p&gt;AI and LLM can step even further in the direction of communication improvement in the future.&lt;/p&gt;
&lt;p&gt;Consider a scenario where Alice needs to tell her colleague Bob to handle a customer request. Instead of Alice trying to guess what context Bob has or lacks, she could give the instruction to her AI assistant rather than communicating with Bob directly. Alice&apos;s AI could then communicate with Bob&apos;s AI, sharing the necessary context and information, ensuring that Bob receives a complete and clear message. This method of using AIs as proxies eliminates the guesswork and ensures that all relevant details are communicated effectively.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/edbb7e88-9bba-4fc5-9d43-081275a40208_727x307.png&quot; alt=&quot;Diagram of AI assistants communicating between Alice and Bob&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;The Vision: AI-Assisted Communication&lt;/h2&gt;
&lt;p&gt;In the future, AI could be integrated into every piece of communication, from emails to meetings to casual conversations. The potential is immense. Imagine AI assistants transforming messages to match the recipient&apos;s preferred communication style and form, embedding the extra context that might be missing to the recipient.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/d6a6575f-1d58-4808-8c46-cdbf2f6f237f_1376x864.webp&quot; alt=&quot;Illustration of AI transforming messages to match the recipient&apos;s communication style&quot; /&gt;&lt;/p&gt;
&lt;p&gt;For example, if Alice’s AI knows that Bob prefers visual information, it could transform Alice’s text-based request into an infographic or a visual summary. This ensures that Bob receives the information in the most effective way for him, enhancing understanding and efficiency.&lt;/p&gt;
&lt;h2&gt;Benefits of AI in Communication&lt;/h2&gt;
&lt;p&gt;The primary benefit of AI-enhanced communication is the significant improvement in efficiency. Misunderstandings and miscommunications can lead to wasted time and resources. By ensuring that all parties have the necessary context, AI can streamline interactions and reduce the need for clarifications and follow-ups.&lt;/p&gt;
&lt;p&gt;Additionally, AI can create a personalized communication experience, tailoring messages to fit the recipient&apos;s preferences and needs. This not only improves comprehension but also makes interactions more pleasant and engaging.&lt;/p&gt;
&lt;h2&gt;Overcoming Challenges&lt;/h2&gt;
&lt;p&gt;However, implementing AI in communication is not without its challenges. One significant issue is the segregation of information. Just as humans struggle with deciding whether to share certain information, AI will need to learn how to handle sensitive or contextual data appropriately. Current AI systems lack robust role-based access control (RBAC) for context, making it difficult to manage which information can be shared and with whom.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/d163612a-f13c-491c-b858-3f0daafd4eef_544x589.png&quot; alt=&quot;Diagram of AI sandboxing cycle for managing sensitive information&quot; /&gt;
&lt;em&gt;Sandboxing Cycle&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Furthermore, while &lt;a href=&quot;https://www.independent.co.uk/life-style/facebook-artificial-intelligence-ai-chatbot-new-language-research-openai-google-a7869706.html&quot;&gt;AI can potentially develop its own languages to communicate more efficiently&lt;/a&gt;, the practical application of this in everyday communication remains a complex challenge. Security and privacy concerns also need to be addressed, ensuring that sensitive information is protected while still allowing AI to function effectively. I don’t think anyone is actively working on this right now, but it will be a major issue in the future.&lt;/p&gt;
&lt;h2&gt;Personal Reflections and Future Visions&lt;/h2&gt;
&lt;p&gt;Reflecting on my own experiences, I&apos;ve often encountered situations where additional context could have prevented misunderstandings. A really simple example would be planning a lunch meeting without knowing the dietary preferences of your invitee. It might be so obvious to your guest that the restaurant must have vegan options that will not mention it, which can lead to disappointing outcomes if you book a steak house. If AI can provide this context seamlessly, such issues could be avoided.&lt;/p&gt;
&lt;p&gt;Looking ahead, I envision AI assistants like Siri evolving to incorporate these advanced communication capabilities. This could extend beyond personal assistants to systems built between products and companies, facilitating smoother interactions and collaborations across different platforms.&lt;/p&gt;
&lt;p&gt;The future of AI in communication holds incredible promise. By bridging the gaps in context and understanding, AI has the potential to transform how we interact, making communication more efficient and effective. Of course, getting there requires solving a fundamental problem: &lt;a href=&quot;https://julien.danjou.info/blog/ai-is-a-human-interface-nightmare&quot;&gt;AI is still a human interface nightmare&lt;/a&gt;, and the way we interact with these systems today is far from what it could be. While challenges remain, the ongoing advancements in AI technology bring us closer to a future where misunderstandings are minimized and every message is clearly understood.&lt;/p&gt;
</content:encoded></item><item><title>Reflecting on the Journey of &quot;Nom d&apos;un Pipeline !&quot;</title><link>https://julien.danjou.info/blog/reflecting-on-the-journey-of-nom/</link><guid isPermaLink="true">https://julien.danjou.info/blog/reflecting-on-the-journey-of-nom/</guid><description>A Podcast About CI/CD for the French Tech Scene</description><pubDate>Tue, 30 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&quot;&lt;a href=&quot;https://nomdunpipeline.com&quot;&gt;Nom d&apos;un Pipeline !&lt;/a&gt;&quot; (translation for English readers: &quot;What a Pipeline!&quot;) has been an incredible journey, filled with learning, growth, and connecting with some of the brightest minds in the French tech scene. As a French-based startup for over five years, with most of our customers and audience in the US, we noticed a significant gap in the knowledge and maturity around CI/CD among French engineering teams. This realization motivated us to create a platform to elevate the understanding and practices of CI/CD in our home country, leading to the birth of &quot;Nom d&apos;un Pipeline !&quot;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/37d5f1f1-3eb9-4cae-984c-ba4f49a99615_1323x1323.png&quot; alt=&quot;Nom d&apos;un Pipeline podcast logo&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;The Birth of an Idea&lt;/h2&gt;
&lt;p&gt;Starting a podcast was a new adventure for me. With the help of Mergify’s marketing team, we built everything from the ground up and began reaching out to potential guests. We aimed to find the right candidates and teams to feature in our 45-minute episodes, sharing their journeys and insights around continuous integration, continuous deployment, and quality assurance.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/60f0e810-d2cf-4180-b93a-b71e84f49613_1536x768.webp&quot; alt=&quot;Nom d&apos;un Pipeline podcast banner&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Highlights from Season 1&lt;/h2&gt;
&lt;p&gt;All episodes were amazing, and the content we built was really valuable. I think several episodes stood out for their depth and impact:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.nomdunpipeline.com/episode/definir-identifier-et-tester-pour-performer&quot;&gt;Mathieu Leroux-Huet&lt;/a&gt; discussed performance, offering valuable insights into optimizing systems for better efficiency.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.nomdunpipeline.com/episode/ep-10-faire-des-economies-avec-ses-propres-runners&quot;&gt;Cyril Rohr&lt;/a&gt; from RunsOn shared how improving GitHub Actions runner can significantly enhance CI workflows speed and cost.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.nomdunpipeline.com/episode/ep-6-vers-des-pipelines-ci-cd-imposes-et-standardises-avec-olivier-pillaud-tirard-de-manomano&quot;&gt;Olivier Pillaud-Tirard&lt;/a&gt; from ManoMano detailed how they have improved their CI over the last couple of years, providing a roadmap for other teams to follow.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These episodes were not only informative but also showcased the diverse approaches and innovations happening within the French tech community.&lt;/p&gt;
&lt;h2&gt;Overcoming Challenges&lt;/h2&gt;
&lt;p&gt;Producing a podcast comes with its own set of challenges. One of the biggest hurdles we faced was recording remotely. There were times when technical issues disrupted the recordings, but we managed to overcome these obstacles with perseverance and technical adjustments. While I would love to record in a studio, living in Toulouse makes it challenging since none of my guests are local. Despite these difficulties, the remote setup has allowed us to connect with a broader range of guests.&lt;/p&gt;
&lt;h2&gt;Positive Feedback and Growing Momentum&lt;/h2&gt;
&lt;p&gt;The feedback we&apos;ve received has been overwhelmingly positive. Listeners appreciate the insights and real-world experiences shared by our guests. Knowing that the show provides value and helps the French tech scene advance in CI/CD practices is incredibly rewarding. Recently, we passed the 2,000 views/listens mark, indicating growing momentum after just eight months. This milestone is a testament to the show&apos;s impact and the increasing interest in CI/CD topics.&lt;/p&gt;
&lt;h2&gt;Personal and Professional Growth&lt;/h2&gt;
&lt;p&gt;On a personal level, hosting &quot;Nom d&apos;un Pipeline !&quot; has been a delightful experience. I discovered that I genuinely enjoy talking to people and learning about their teams and tech stacks. It&apos;s been an eye-opening journey that has enriched my understanding of CI/CD and connected me with some brilliant minds in the industry.&lt;/p&gt;
&lt;h2&gt;Acknowledging Our Guests&lt;/h2&gt;
&lt;p&gt;I want to extend my heartfelt thanks to all our guests for their confidence and for sharing their journeys: Clément, Sofiyan, Romaric, Aurélien, Frédéric, Olivier, Dan, Thomas, Mathieu, Cyril and François.&lt;/p&gt;
&lt;p&gt;Your contributions have been invaluable in making this podcast a success.&lt;/p&gt;
&lt;h2&gt;Looking Ahead to Season 2&lt;/h2&gt;
&lt;p&gt;As we wrap up the first season, we are already gearing up for season 2, set to launch in September. We are excited to bring new hosts from major French companies like Doctolib and Alan, promising even more insightful content and engaging discussions. While we don&apos;t have specific plans for season 2 yet, we are committed to continuing our mission of educating and inspiring the French tech community about CI/CD.&lt;/p&gt;
&lt;p&gt;&quot;Nom d&apos;un Pipeline !&quot; has been an extraordinary journey, and I&apos;m proud of what we&apos;ve accomplished so far. We hope that this podcast continues to serve as a valuable resource for the French tech scene, helping teams to improve their continuous integration, testing, continuous deployment, and overall development workflows — including tackling tough topics like &lt;a href=&quot;https://julien.danjou.info/blog/the-challenges-of-merge-queues&quot;&gt;the challenges of merge queues&lt;/a&gt;. Stay tuned for more exciting episodes and insights in the upcoming season!&lt;/p&gt;
&lt;p&gt;If you understand French, don’t forget to subscribe to the podcast &lt;a href=&quot;https://www.nomdunpipeline.com/&quot;&gt;on your favorite podcast platform&lt;/a&gt; or on &lt;a href=&quot;https://www.youtube.com/@NomdunPipeline&quot;&gt;YouTube&lt;/a&gt;!&lt;/p&gt;
</content:encoded></item><item><title>The Challenges of Merge Queues</title><link>https://julien.danjou.info/blog/the-challenges-of-merge-queues/</link><guid isPermaLink="true">https://julien.danjou.info/blog/the-challenges-of-merge-queues/</guid><description>Why They’re Hard and Why We’re Simplifying Them</description><pubDate>Tue, 23 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Merge queues are a tough concept to grasp, and over the last five years at &lt;a href=&quot;https://mergify.com&quot;&gt;Mergify&lt;/a&gt;, we&apos;ve spent countless hours educating developers about their importance and utility. We&apos;ve published numerous blog posts, written extensive documentation, and even gone to conferences to teach software engineers what a merge queue is. This process of spreading awareness has been a rewarding yet challenging endeavor.&lt;/p&gt;
&lt;p&gt;One of our developers, Charly Laurent, gave an insightful talk on the subject, highlighting how merge queues can revolutionize CI/CD processes. You can check out his talk here:&lt;/p&gt;
&lt;h2&gt;Understanding Merge Queues&lt;/h2&gt;
&lt;p&gt;Merge queues are not an obvious choice for most teams, and they often require a shift in the balance between safety and speed of delivery. Deploying a merge queue means prioritizing quality over quantity, which is not an easy decision for many development teams, who might be pressured to ship fast.&lt;/p&gt;
&lt;p&gt;For example, without a merge queue, teams often merge untested code. This is due to outdated test runs, meaning that they are deploying code that might not work. Without a merge queue, there is no way to prevent merging pull requests with outdated tests and breaking the CI for everyone — which is exactly why you should &lt;a href=&quot;https://julien.danjou.info/blog/stop-merging-your-pull-request-manually&quot;&gt;stop merging your pull requests manually&lt;/a&gt; in the first place. One of our customers faced this exact issue, which meant they needed the equivalent of a full-time engineer dedicated to tracking issues in the main branch that broke the CI.&lt;/p&gt;
&lt;p&gt;Most platform engineers find the concept of moving the post-merge tests to pre-merge tests challenging.&lt;/p&gt;
&lt;h2&gt;The Trade-offs&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://vercel.com/blog/deploy-safely-on-vercel-without-merge-queues&quot;&gt;This blog post from Vercel&lt;/a&gt; captures this common misunderstanding and the trade-off around merge queues and their CI costs and latency:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Despite the majority of commits being safe to merge after the local CI checks complete on their pull request, the merge queue will incur running the cost of running the CI again every time.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;While this is true, the problem here lies in the word &quot;majority.&quot; The definition of &quot;majority&quot; can vary significantly across teams. If a minority of pull requests break the main branch after merging, it can cause considerable downtime and require substantial effort from CI engineers to restore stability. We&apos;ve seen teams come to Mergify with a 30% failure rate on their &lt;code&gt;main&lt;/code&gt; branch. While a merge queue won&apos;t magically improve the failure rate, it ensures that it doesn&apos;t worsen, even if it means a small decrease in merge speed. That ensures that the effort invested in improving the CI is not wasted the day after.&lt;/p&gt;
&lt;p&gt;Another perspective from Vercel states:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;With merge queues, changes from developers depend on changes from other developers even if they are unrelated to each other, and this makes it hard to scale monorepo merge times with more developers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This concern is valid for merge queues that don&apos;t support monorepo and queue parallelization. However, most modern merge queues (GitHub&apos;s own being an exception) do allow for optimization in these scenarios.&lt;/p&gt;
&lt;p&gt;Vercel’s blog post concludes with:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;With this workflow in place, the merge queue can be safely removed because checks will still always be run before users ever see the deployment.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This reflects the workflow of many teams that don&apos;t use a merge queue: merge, run tests on &lt;code&gt;main&lt;/code&gt;, then deploy. However, this approach doesn&apos;t solve the issue of merging something that breaks the main branch. During the downtime, teams have to identify the culprit, revert changes, and ensure everything works, causing delays and frustration. Bad developer experience ensues.&lt;/p&gt;
&lt;p&gt;Teams like &lt;a href=&quot;https://www.uber.com/blog/research/keeping-master-green-at-scale/&quot;&gt;Uber recognized this problem six years ago&lt;/a&gt; and started building their merge queues. Similarly, in OpenStack, we had a system supporting multiple repositories with &lt;a href=&quot;https://zuul-ci.org/&quot;&gt;Zuul&lt;/a&gt; over ten years ago.&lt;/p&gt;
&lt;h2&gt;Build New Solutions&lt;/h2&gt;
&lt;p&gt;Considering the merge queue adoption issues, we&apos;ve spent the last few months reworking our merge queue system to simplify deployment and enhance user experience. We know for a fact that developers appreciate the reliability it brings to CI processes, but we also observe the difficulty of discovering and integrating the system. By deploying a merge queue, teams can eliminate the need for a &quot;check that main works before deployment&quot; step because this is done before the actual merge.&lt;/p&gt;
&lt;p&gt;One notable example is a team that previously needed a full-time engineer to manage CI issues due to frequent breaks in the &lt;code&gt;main&lt;/code&gt; branch. After adopting Mergify&apos;s merge queue, they drastically reduced these disruptions, allowing their engineers to focus on more productive tasks.&lt;/p&gt;
&lt;h2&gt;The Road Ahead&lt;/h2&gt;
&lt;p&gt;Merge queues are not without their challenges, and the trade-offs between safety and speed are not always apparent. However, we believe in their potential to transform development workflows. We&apos;re on the verge of redefining the merge queue concept at Mergify, and we think it has far greater potential than what has been realized over the past decade. I’ll be happy to write about that soon and share what we’ve built.&lt;/p&gt;
</content:encoded></item><item><title>Navigating SQL Migrations with Confidence: Introducing sql-compare</title><link>https://julien.danjou.info/blog/navigating-sql-migrations-with-confidence/</link><guid isPermaLink="true">https://julien.danjou.info/blog/navigating-sql-migrations-with-confidence/</guid><description>Delivering SQL schema change at scale.</description><pubDate>Tue, 16 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As long as I can remember, SQL has been a cornerstone of my engineering journey. My early days at university were filled with monotonous Oracle-based SQL courses, which I found uninspiring. Knowing I would likely never use Oracle, I shifted my focus to &lt;a href=&quot;https://mysql.com&quot;&gt;MySQL&lt;/a&gt;. Over time, I discovered the limitations of MySQL and was introduced to &lt;a href=&quot;https://www.postgresql.org/&quot;&gt;PostgreSQL&lt;/a&gt;, thanks to &lt;a href=&quot;https://tapoueh.org/about/&quot;&gt;Dimitri&lt;/a&gt;. I even organized a few meetups in Paris and encouraged Dimitri to publish &quot;&lt;a href=&quot;https://theartofpostgresql.com/&quot;&gt;The Art of PostgreSQL&lt;/a&gt;,&quot; arguably the best book on SQL (&lt;a href=&quot;https://julien.danjou.info/blog/the-art-of-postgresql-is-out&quot;&gt;I reviewed it here&lt;/a&gt;). Eventually, I embraced PostgreSQL wholeheartedly.&lt;/p&gt;
&lt;p&gt;SQL databases are a timeless technology that continues to evolve. From &lt;a href=&quot;https://www.timescale.com/&quot;&gt;Timescale&lt;/a&gt; to &lt;a href=&quot;https://github.com/pgvector/pgvector&quot;&gt;pgvector&lt;/a&gt;, new advancements are continually emerging. However, one persistent challenge has been managing database migrations. Modifying your data model is crucial for evolving your application, but it’s often a daunting task. At Mergify, like many companies, we’ve faced this challenge head-on.&lt;/p&gt;
&lt;p&gt;We&apos;ve tried various solutions, from custom Python scripts to using &lt;a href=&quot;https://github.com/djrobstep/migra&quot;&gt;migra&lt;/a&gt;, an open-source project that is unfortunately no longer maintained. Each solution had its drawbacks, leading us to a crossroads where we had to decide on our next move.&lt;/p&gt;
&lt;h2&gt;The Initial Struggle&lt;/h2&gt;
&lt;p&gt;At &lt;a href=&quot;https://mergify.com&quot;&gt;Mergify&lt;/a&gt;, PostgreSQL is the backbone of our data handling, from managing the state of GitHub objects to maintaining our event log. From the beginning, we’ve interacted with the database exclusively using an ORM, choosing &lt;a href=&quot;https://www.sqlalchemy.org/&quot;&gt;SQLAlchemy&lt;/a&gt; for its maturity, framework agnosticism, and support for asynchronous I/O since version 2.0.0.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/81ae39c3-f945-42cc-80c0-99ade9f0bc9f_1456x816.webp&quot; alt=&quot;Illustration of SQL database migration workflow&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Given our frequent production deployments, a robust CI/CD pipeline is essential to handle database evolution smoothly. Every schema modification must be rigorously tested and automatically applied to the production database, adhering to the principles outlined in Martin Fowler&apos;s &quot;&lt;a href=&quot;https://martinfowler.com/articles/evodb.html&quot;&gt;Evolutionary Database Design.&lt;/a&gt;&quot; Version-controlling each database artifact and scripting every change as a migration are critical steps in this process.&lt;/p&gt;
&lt;p&gt;We chose &lt;a href=&quot;https://alembic.sqlalchemy.org/&quot;&gt;Alembic&lt;/a&gt; to manage our database migrations. Maintained by the SQLAlchemy team, Alembic is a command-line tool that can automatically create migration scripts from your SQLAlchemy models. Each script is version-controlled alongside your source code. Alembic applies these migrations to the database, recording the revision number in the &lt;code&gt;alembic_version&lt;/code&gt; table to ensure only new migrations are applied subsequently. This command is typically executed in the continuous delivery pipeline to keep the production database up-to-date.&lt;/p&gt;
&lt;h2&gt;A Naive Beginning&lt;/h2&gt;
&lt;p&gt;Our initial approach to testing migration scripts was straightforward: create two databases—one using SQLAlchemy models and the other using only the migration scripts—and ensure they have identical schemas. This involved:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Creating PostgreSQL servers using Docker:&lt;/strong&gt; On a new server, create two empty databases.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Generating schemas:&lt;/strong&gt; Use the first database to create artifacts with SQLAlchemy models, and use Alembic to run migration scripts on the second database.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Comparing schemas:&lt;/strong&gt; Dump each database schema into SQL files using &lt;code&gt;pg_dump&lt;/code&gt; and compare them using Python’s &lt;code&gt;filecmp&lt;/code&gt; and &lt;code&gt;difflib&lt;/code&gt; builtin libraries.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here’s an example command to dump a database schema into an SQL file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pg_dump \
    --dbname=postgresql://user:password@host:port/database \
    --schema-only \
    --exclude-table=alembic_version \
    --format=p \
    --encoding=UTF8 \
    --file /path/to/dump.sql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To compare the files:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;assert filecmp.cmp(schema_dump_creation_path, schema_dump_migration_path, shallow=False)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the test fails, use &lt;code&gt;difflib&lt;/code&gt; to display the differences:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def filediff(path1: pathlib.Path, path2: pathlib.Path) -&amp;gt; str:
    with path1.open() as f1, path2.open() as f2:
        diff = difflib.unified_diff(
            f1.readlines(),
            f2.readlines(),
            path1.name,
            path2.name,
        )
        return &quot;Database dump differences: \n&quot; + &quot;&quot;.join(diff)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While effective, this test had limitations, such as sensitivity to column order. PostgreSQL doesn’t easily allow changing column positions, necessitating consistent column order in models and production databases.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/f7a3603f-e06b-45d7-97c4-99f9bbb3da76_1456x816.png&quot; alt=&quot;Illustration of comparing database schemas side by side&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;The Complexity Grows&lt;/h2&gt;
&lt;p&gt;As our models grew more complex, our naive test struggled to keep up. Consider the following example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Base:
    updated_at: orm.Mapped[datetime.datetime] = orm.mapped_column(
        sqlalchemy.DateTime(timezone=True),
        server_default=sqlalchemy.func.now(),
    )

class User(Base):
    id: orm.Mapped[int] = orm.mapped_column(
        sqlalchemy.BigInteger,
        primary_key=True,
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this setup, the &lt;code&gt;updated_at&lt;/code&gt; column is added to every child model, such as &lt;code&gt;User&lt;/code&gt;. Adding a new column to &lt;code&gt;User&lt;/code&gt;, like &lt;code&gt;name&lt;/code&gt;, would misalign the order, causing schema mismatches.&lt;/p&gt;
&lt;p&gt;To address this, we needed to compare schemas while ignoring column order. We explored various tools:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Alembic&lt;/strong&gt;: Can compare schemas to generate migration scripts but misses some differences.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Migra&lt;/strong&gt;: An unmaintained tool that compares database schemas effectively.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SQL dumps&lt;/strong&gt;: The most reliable format but challenging to parse and compare directly.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Building the Solution: sql-compare&lt;/h2&gt;
&lt;p&gt;It was clear that our current solutions were insufficient. We needed a hero to rescue us from the perils of SQL migration management, so we developed &lt;strong&gt;&lt;a href=&quot;https://github.com/Mergifyio/sql-compare&quot;&gt;sql-compare&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;sql-compare is a Python library that uses &lt;a href=&quot;https://pypi.org/project/sqlparse/&quot;&gt;sqlparse&lt;/a&gt; to parse SQL files and compare schemas, ignoring irrelevant differences like comments, whitespace, and column order. This new tool became an integral part of our workflow, catching migration issues that other tools might miss.&lt;/p&gt;
&lt;p&gt;The main challenge was filtering and grouping tokens by column definition before sorting them. Despite these complexities, sql-compare emerged victorious, enabling us to ensure seamless migrations and maintain schema integrity.&lt;/p&gt;
&lt;h2&gt;The Journey Forward&lt;/h2&gt;
&lt;p&gt;We’ve open-sourced sql-compare to help others facing similar challenges. You can try it by running &lt;code&gt;pip install sql-compare&lt;/code&gt;. We plan to enhance sql-compare, such as creating functions to retrieve all schema differences for better test results. If you have suggestions or want to contribute, feel free to submit issues or pull requests on our GitHub repository.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Managing database migrations is a complex but essential task for evolving applications. With sql-compare, we found our solution, ensuring seamless migrations, maintaining schema integrity, and continuing to deliver high-quality software. Our journey through the challenges of SQL migrations has taught us valuable lessons, and with sql-compare, we’re better equipped to face the future.&lt;/p&gt;
</content:encoded></item><item><title>A Journey of Embracing Linters</title><link>https://julien.danjou.info/blog/the-journey-of-embracing-linters/</link><guid isPermaLink="true">https://julien.danjou.info/blog/the-journey-of-embracing-linters/</guid><description>perl -e &apos;use strict;&apos;</description><pubDate>Tue, 09 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recently, I found myself in a spirited debate with one of our front-end developers at &lt;a href=&quot;https://mergify.com&quot;&gt;Mergify&lt;/a&gt;. This discussion, revolving around the usage of linters, reminded me of my long and storied history with these &quot;advisor tools.&quot; Having been confronted with linters for the past 25 years, I believe it&apos;s time to share some of that accumulated wisdom.&lt;/p&gt;
&lt;p&gt;My first encounter with a linter was with &lt;code&gt;use strict&lt;/code&gt; in &lt;a href=&quot;https://www.perl.org/&quot;&gt;Perl&lt;/a&gt;. Although I can&apos;t recall the specifics of what it did, I do remember it being an essential tool for writing better code. Later on, I encountered the &lt;code&gt;gcc -W&lt;/code&gt; and &lt;code&gt;-pedantic&lt;/code&gt; options, which I enabled religiously in all my projects. These early experiences set the stage for my ongoing relationship with linters.&lt;/p&gt;
&lt;h2&gt;Warnings&lt;/h2&gt;
&lt;p&gt;Fast forward to today, my recent discussion centered around &lt;a href=&quot;https://eslint.org/&quot;&gt;eslint&lt;/a&gt; and enabling all the checks for the Playwright plugin, treating every drift as an error rather than a warning. This distinction is crucial: an error causes the CI to fail, while a warning merely generates noise. Not all linters have this warning level, but in my experience, warnings will be disruptive if left unaddressed in your development workflow. An error should be a clear-cut issue: either ignore it or fix it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Having unresolved warnings in your CI logs creates ambiguity and inefficiency. Make a decision. Commit to it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/2966edeb-e81d-45e4-b2c4-a82215b7812b_1382x304.png&quot; alt=&quot;Screenshot of the eslint warning that triggered the linter discussion&quot; /&gt;
&lt;em&gt;The original warning that triggered our discussion.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Picking Errors&lt;/h2&gt;
&lt;p&gt;Despite not being a JavaScript expert, my 25 years of experience with various linters gives me some perspective on this matter. Our debate also touched on which approach to use with respect to linters, either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;stick to the recommended and default settings;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;being stricter by promoting certain warnings to errors for checks we deemed useful;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;enable everything to be an error and explicitly ignore checks that don&apos;t apply to our project or are considered incorrect.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Every linter, from &lt;code&gt;gcc -W&lt;/code&gt; flags to &lt;a href=&quot;https://docs.astral.sh/ruff/&quot;&gt;ruff&lt;/a&gt; in Python, starts with a set of &quot;recommended&quot; settings. These are designed to throw a manageable number of errors on a typical project, making the linter easy to adopt for teams. This doesn&apos;t mean the disabled options are bad; they are simply considered &quot;too much for beginners&quot; and can be enabled later.&lt;/p&gt;
&lt;p&gt;This incremental approach is how we adopted &lt;a href=&quot;https://mypy-lang.org/&quot;&gt;mypy&lt;/a&gt; at Mergify. The default typing checks are relatively light, allowing us to enable it without much friction. We spent a few weeks fixing typing issues, caught a few bugs in the process, and were satisfied. Gradually, we enabled more checks until we reached the point of enabling &lt;code&gt;strict = true&lt;/code&gt; (a nostalgic nod to Perl) and caught even more (potential) bugs.&lt;/p&gt;
&lt;p&gt;On the flip side, having a poorly calibrated set of default recommendations is why I never adopted &lt;a href=&quot;https://www.pylint.org/&quot;&gt;pylint&lt;/a&gt;. Running pylint on our otherwise impeccable Python code, which passes ruff with most checks enabled, results in 13,000 errors for 140,000 SLOC. (I wrote about similar code quality tools in &lt;a href=&quot;https://julien.danjou.info/blog/the-best-flake8-extensions&quot;&gt;The Best Flake8 Extensions&lt;/a&gt;.) This is an insurmountable barrier for any developer. The prospect of ignoring all these non-critical errors, such as missing docstrings or line lengths, seems daunting.&lt;/p&gt;
&lt;h2&gt;Eslint and Playwright&lt;/h2&gt;
&lt;p&gt;Returning to eslint and &lt;a href=&quot;https://playwright.dev/&quot;&gt;Playwright&lt;/a&gt;, we used the following code to enable all Playwright rules as errors:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...Object.keys(playwrightPlugin.configs[&apos;flat/recommended&apos;].plugins.playwright.rules).reduce(
  (acc, rule) =&amp;gt; {
    acc[`playwright/${rule}`] = &apos;error&apos;;
    return acc;
  },
  {}
),
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This approach ensures we don&apos;t miss any linting recommendations from the Playwright team. With &lt;a href=&quot;https://docs.github.com/en/code-security/getting-started/dependabot-quickstart-guide&quot;&gt;Dependabot&lt;/a&gt; automatically updating our dependencies, new errors introduced by updates appear in brand-new pull requests, allowing us to improve our code continuously.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/b46b3de6-8fa1-4445-9434-da42a5fbe88b_1536x768.png&quot; alt=&quot;Illustration of enabling all linter checks and treating warnings as errors&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In conclusion, &quot;recommended&quot; settings in linters are designed for ease of adoption, striking a balance between &quot;best practices&quot; and &quot;practicality,&quot; but they are not guidelines for what you should follow.&lt;/p&gt;
&lt;p&gt;Striving for perfection (assuming your linter is robust and not crazy) is always the goal. Make deliberate choices about which checks to ignore, and remember that linters are here to help you write better, more reliable code.&lt;/p&gt;
</content:encoded></item><item><title>The Biggest Mistake We Made Building Mergify: Navigating the Hiring Minefield</title><link>https://julien.danjou.info/blog/the-biggest-mistake-we-made-building-a43/</link><guid isPermaLink="true">https://julien.danjou.info/blog/the-biggest-mistake-we-made-building-a43/</guid><description>Possibly the hardest part of the job.</description><pubDate>Tue, 02 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;This is part 2 of the &quot;Biggest Mistakes&quot; series. Read part 1: &lt;a href=&quot;https://julien.danjou.info/blog/the-biggest-mistake-we-made-building&quot;&gt;Navigating the Payment System Nightmare&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Building a successful startup is a journey filled with unexpected challenges, and hiring the right people is undoubtedly one of the most daunting tasks. As a tech engineer with no HR background, I’ve faced numerous hiring pitfalls that have taught me invaluable lessons. Reflecting on our journey at &lt;a href=&quot;https://mergify.com&quot;&gt;Mergify&lt;/a&gt;, it&apos;s clear that navigating the complexities of hiring has been one of the biggest challenges we&apos;ve encountered. Here’s what we’ve learned.&lt;/p&gt;
&lt;h2&gt;Who to Hire: The Temptation of Cheap Labor&lt;/h2&gt;
&lt;p&gt;When you’re a startup with limited resources, it’s tempting to hire interns or apprentices, especially when they come at a low or even zero cost, thanks to government sponsorships. In France, this is an attractive option many startups pursue, and we were no different. Initially, we hired interns and apprentices, believing that this would provide us with much-needed help without straining our budget.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/1eeb04a8-72d7-4d76-b1d7-1ff056e39f80_1536x768.png&quot; alt=&quot;Illustration of the temptation of hiring interns and cheap labor at a startup&quot; /&gt;&lt;/p&gt;
&lt;p&gt;However, we quickly realized that while interns can be a valuable addition, they often lack the expertise we needed to tackle complex tasks. As founders, we required skilled assistance, not just extra hands for minor tasks. The overhead of breaking down projects into manageable tasks and guiding interns through them often resulted in a net loss of productivity. While we did encounter some exceptional interns who became valuable team members, the general rule is that relying on almost-free resources like interns is unlikely to provide the expertise needed in the early days of your startup.&lt;/p&gt;
&lt;h2&gt;How to Hire: The Challenge of Assessing Candidates&lt;/h2&gt;
&lt;p&gt;Hiring is more than just finding someone with the right skills; it’s about finding the right fit for your team and company culture. Despite the plethora of online resources on evaluating candidates, the reality of assessing someone over a Zoom call in just a few hours is incredibly challenging.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We&apos;ve had both terrific and terrible hires, and in each case, we believed they were a perfect fit at the time.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;After conducting hundreds of interviews and hiring more than a dozen people, we’ve learned one golden rule: if you have any doubts about a candidate, don’t hire them. It’s better to wait for the right person than to rush and hire someone who might not fit well with your team.&lt;/p&gt;
&lt;h2&gt;Finding Candidates: The Perils of Recruitment&lt;/h2&gt;
&lt;p&gt;The search for talent is a constant struggle. In France, we experimented with various solutions, from Welcome to the Jungle (a nightmare) to &lt;a href=&quot;https://talent.io&quot;&gt;talent.io&lt;/a&gt; (effective). We also engaged a few headhunting firms, which unfortunately turned out to be a costly mistake. These firms often sent us unsuitable candidates and still kept their fees. The legal obligations in France favor the headhunters, not the employers, making it a risky and expensive endeavor.&lt;/p&gt;
&lt;p&gt;For example, we had a candidate that we hired and then paid the fees to the headhunting firm. The employee would stop their trial period and leave. That meant the headhunting firm should send new candidates our way to replace the one that left during the trial period. However, there was no incentive for them to do so as they’d been paid already. Therefore, they didn’t care. We found our candidate in a different manner, meaning the fee was then lost for us. Considering that fees can be 10–20% of the employee&apos;s yearly salary, that’s a large amount of money to throw out the window.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/df7cb53f-1b32-4f33-9622-ca8767df181e_1536x768.webp&quot; alt=&quot;Illustration of the challenges of finding talent through headhunters and recruitment&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Networking and recommendations remain some of the best ways to find talent, but they don’t scale well and often have timing issues; you’d find the right candidate, but they’re not available, or a friend would knock at the door, and you’d not have any budget to hire them.&lt;/p&gt;
&lt;p&gt;Additionally, we realized that marketing our company effectively during hiring talks is crucial. Initially, our pitch didn’t resonate, and most candidates would ignore us. By improving our presentation and emphasizing our company culture and values, we started attracting genuinely interested candidates.&lt;/p&gt;
&lt;p&gt;After years, we discovered that you want to &lt;em&gt;polarize&lt;/em&gt; your candidate early in the process and during their employment to ensure they get 100 % onboard with your venture. Depending on your founder profile, that might be something you do naturally. As tech founders, we were not particularly good at that, but we learned our way through.&lt;/p&gt;
&lt;h2&gt;The Remote Work Dilemma&lt;/h2&gt;
&lt;p&gt;Building a remote team has advantages, like accessing a broader talent pool, but it also comes with significant challenges. At Mergify, we embraced remote work and &lt;a href=&quot;https://blog.mergify.com/embracing-remote-work-how-we-built-mergify-as-a-successful-asynchronous-company/&quot;&gt;wrote extensively about our experience&lt;/a&gt;. Remote work works well with senior staff, but junior employees often struggle without in-person guidance. Sharing the company vision and brainstorming ideas are also more effective in person, which is why we regularly organize in-person meetings.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/2aad7a8b-4718-4807-9bc0-48c702dd110d_1536x768.png&quot; alt=&quot;Illustration of remote work challenges and team building&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Regular team-building events, video calls, and asynchronous communication help bridge the gap, but they can’t completely replace the spontaneous interactions that foster innovation. Remote work is great for finding talent, but in-person connections remain essential for a cohesive and innovative team environment.&lt;/p&gt;
&lt;p&gt;I would not consider remote work a mistake, but we underestimated its impact on the company.&lt;/p&gt;
&lt;h2&gt;Lessons Learned and Rules Established&lt;/h2&gt;
&lt;p&gt;From our hiring mistakes, we’ve developed a few key rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Avoid hiring remote junior staff if you are working remotely. They need more guidance and in-person interaction.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leverage in-person connections for innovation. Remote work makes this challenging, especially for junior staff.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Share a lot of context to drive innovation and execution. Overcommunicate.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Be cautious of headhunters and their fees. Consider delaying payment until the trial period ends.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Avoid working with multiple headhunting firms to avoid finding your candidate with one when you have already paid the other.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Learn to pitch your company effectively. Highlight your values and culture to attract the right candidates.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you have any doubts about a candidate, don’t hire them.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Don’t hire interns and trainees until you have enough senior staff to mentor them. Consider them a small cost, not a huge win.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Navigating the hiring process is complex and fraught with potential pitfalls, but by learning from our mistakes and establishing clear rules, we&apos;ve been able to build a stronger, more effective team.&lt;/p&gt;
&lt;p&gt;If you’re building a startup, remember that the right hires can make all the difference, and taking the time to find them is well worth the effort.&lt;/p&gt;
</content:encoded></item><item><title>Discovering the Tech Community in Toulouse: A Three-Year Journey</title><link>https://julien.danjou.info/blog/discovering-the-tech-community-in/</link><guid isPermaLink="true">https://julien.danjou.info/blog/discovering-the-tech-community-in/</guid><description>It&apos;s been 3 years now since I moved from Paris to Toulouse.</description><pubDate>Tue, 25 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When I moved to Toulouse three years ago, I knew almost no one. My only connection was my cofounder, &lt;a href=&quot;http://sileht.net&quot;&gt;Mehdi&lt;/a&gt;. It was an intimidating start, but thanks to the magic of &lt;a href=&quot;https://www.linkedin.com/posts/juliendanjou_toulouse-remotework-activity-6807626256461414400-th73?utm_source=share&amp;amp;utm_medium=member_desktop&quot;&gt;LinkedIn&lt;/a&gt;, my presence in the city didn&apos;t go unnoticed. Soon, people reached out to me, giving me my first glimpse into the tech scene here. Among those early connections were Denis and Cédric, whose introductions helped bootstrap my network at an impressive speed.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/6990de8c-aa50-42bf-afd6-5fa15a19f7b5_1032x780.webp&quot; alt=&quot;Map showing Toulouse, fourth largest city in France and home of Airbus&quot; /&gt;
&lt;em&gt;In case you have never heard of Toulouse, it’s the fourth largest city in France (soon to be third), with 0.5 million inhabitants, and the home of Airbus.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Mergify is a remote-first company, so we don’t have an office where I can commute every day and rely on colleagues to easily have a social network. That forced me to go out and expand my social network out in the unknown.&lt;/p&gt;
&lt;p&gt;In 2022, I decided to reboot the &lt;a href=&quot;https://www.meetup.com/fr-FR/python-toulouse/&quot;&gt;Toulouse Python Meetup&lt;/a&gt;. Despite having over 800 members in the group at the time, the event had gone dormant since COVID 19. I reached out to the previous (idle) organizers, and with the support of Hugo at Mergify, we organized our first meetup. (I wrote more about &lt;a href=&quot;https://julien.danjou.info/blog/attending-conferences&quot;&gt;attending conferences&lt;/a&gt; and how my approach changed over the years.) I prepared a quick return of experience talk, which I presented to the three attendees who showed up (!) how we deployed &lt;a href=&quot;https://mypy-lang.org/&quot;&gt;mypy&lt;/a&gt; at Mergify.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/b812719b-1ad1-4283-8af9-2cfcec62bbc0_2161x1217.png&quot; alt=&quot;Photo of the Toulouse Python Meetup session&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It was a humble beginning, but it was a start. Reviving in-person events was still challenging, but we persisted, organizing more meetups until the community began to grow. This effort eventually led us to Wannes, who now runs the events, allowing me to focus on other commitments. It’s tough to get people to attend events nowadays, possibly due to the lingering effects of COVID and the convenience of YouTube.&lt;/p&gt;
&lt;p&gt;Joining the &lt;a href=&quot;https://tech.rocks/&quot;&gt;Tech.Rocks&lt;/a&gt; a couple of years ago was another significant milestone. Through this community, I discovered a substantial number of tech professionals in and around Toulouse. Remote work has made it possible for people to work far from their office, and I met people working at companies such as Datadog, Elastic, Spotify, OVHcloud, Ankorstore, AWS, Scaleway, MongoDB, ManoMano, Malt, Zenchef, GitLab — and there are many others I have yet to meet. I regularly organize lunches and dinners with tech folks from this community to foster stronger bonds, which has been incredibly rewarding.&lt;/p&gt;
&lt;p&gt;I&apos;ve had the pleasure of meeting remarkable people, from experienced engineers to startup founders. Toulouse boasts a vibrant ecosystem, smaller than Paris but thriving nonetheless. While it&apos;s easy to joke about the tech economy here being heavily dependent on Airbus and its providers, that&apos;s not entirely true. There&apos;s a diverse range of companies, though the prevalence of service companies (ESNs) in France is notable, with a scarcity of product-based firms.&lt;/p&gt;
&lt;p&gt;One of the highlights of the tech calendar here is &lt;a href=&quot;https://devfesttoulouse.fr/&quot;&gt;DevFest&lt;/a&gt;, an annual conference organized by the &lt;a href=&quot;https://www.gdgtoulouse.fr/&quot;&gt;Google Developers Group (GDG)&lt;/a&gt;. It&apos;s one of the most active meetups, although the connection to Google isn&apos;t always clear. Nonetheless, it&apos;s a fantastic event that brings the community together.&lt;/p&gt;
&lt;p&gt;As a business angel, I&apos;ve had the opportunity to meet incredible founders and teams from startups like &lt;a href=&quot;https://www.munityapps.com/&quot;&gt;Munity&lt;/a&gt;, &lt;a href=&quot;https://kotzilla.io/&quot;&gt;Kotzilla&lt;/a&gt;, &lt;a href=&quot;https://www.roundtable.eu/&quot;&gt;Roundtable&lt;/a&gt;, and more. It&apos;s inspiring to see startups in Toulouse that aren&apos;t solely focused on aerospace or IoT. There&apos;s a budding entrepreneurial spirit here that’s encouraging to witness.&lt;/p&gt;
&lt;p&gt;Finally, I must mention &lt;a href=&quot;https://www.iot-valley.fr/&quot;&gt;IoT Valley&lt;/a&gt; — it took me three years to understand what was that IoT Valley I heard about all the time. Originally centered around SigFox, the community has evolved and now encompasses a broader range of startups. I recently had the chance to host a dinner and podcast episode there, giving me a deeper understanding of its scope. Located near Toulouse in Labège, IoT Valley houses numerous companies, many still focused on IoT, but expanding into other areas as well. They might benefit from a rebrand, but marketing isn&apos;t typically a strong suit in French tech. Sharing my experiences of building &lt;a href=&quot;https://mergify.com&quot;&gt;Mergify&lt;/a&gt; and working as an entrepreneur and business angel was a highlight, and I look forward to the podcast episode going live.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/553be933-d17c-4b64-b7ed-8d68532ba5b2_2048x1472.webp&quot; alt=&quot;Photo of the IoT Valley Founder Dinner&quot; /&gt;
&lt;em&gt;IoT Valley Founder Dinner&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You can listen to the podcast I recorded &lt;a href=&quot;https://www.iot-valley.fr/podcast/37-les-perspectives-sur-lavenir-de-la-tech-avec-julien-danjou&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;iframe style=&quot;border-radius:12px&quot; src=&quot;https://widget.ausha.co/index.html?podcastId=yX4JgC5WWna7&amp;amp;color=%23ffffff&amp;amp;v=3&quot; width=&quot;100%&quot; frameborder=&quot;0&quot; scrolling=&quot;no&quot;&amp;gt;&amp;lt;/iframe&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In conclusion, Toulouse&apos;s tech scene is dynamic and growing. For anyone considering a move to this city, you&apos;ll find a community that&apos;s welcoming, innovative, and full of opportunities. Whether you&apos;re looking to network, learn, or launch your next venture, Toulouse has something to offer.&lt;/p&gt;
&lt;p&gt;And if you move there, &lt;a href=&quot;mailto:julien@danjou.info&quot;&gt;send me a mail&lt;/a&gt;!&lt;/p&gt;
</content:encoded></item><item><title>A Decade of Writing Books and Selling 25,000 Copies</title><link>https://julien.danjou.info/blog/a-decade-of-writing-books-and-selling/</link><guid isPermaLink="true">https://julien.danjou.info/blog/a-decade-of-writing-books-and-selling/</guid><description>Reflecting on the Journey and Impact of Writing Technical Books.</description><pubDate>Wed, 12 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Ten years ago, I embarked on a journey that profoundly shaped my career and personal growth. Writing my first book, &lt;em&gt;The Hacker&apos;s Guide to Python&lt;/em&gt; (later updated and renamed &lt;em&gt;Serious Python&lt;/em&gt;), marked the beginning of a series of literary endeavors that allowed me to share my knowledge, experiences, and passion for Python programming with a global audience. Today, I reflect on this journey, the lessons learned, and the incredible milestones achieved along the way.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/a7a65530-4515-43ba-b7d2-2dc29f8af2af_2448x3264.webp&quot; alt=&quot;First print of The Hacker&apos;s Guide to Python in 2014&quot; /&gt;
&lt;em&gt;First print of The Hacker’s Guide to Python in 2014&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;The Genesis: The Hacker&apos;s Guide to Python&lt;/h2&gt;
&lt;p&gt;In March 2014, I published my first book, &lt;em&gt;The Hacker&apos;s Guide to Python&lt;/em&gt;. This book was born out of a desire to provide a comprehensive resource for Python developers, offering insights and techniques I had gathered over the years. The response was overwhelmingly positive, and it motivated me to continue writing and sharing my expertise. I sold over 3,000 copies of the book in a couple of years, which is a very good number in its category.&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;Challenges and Time Investment&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Writing was an enormous undertaking that demanded significant time and effort. I spent around 150 hours on each book, covering various activities from writing and editing to marketing and publishing. The process was spread over a year most of the time.&lt;/p&gt;
&lt;p&gt;One of the toughest challenges was constructing a coherent and comprehensive table of contents. This initial step was crucial, as it guided the entire writing process, making the subsequent task of filling in the blanks somewhat more manageable. Additionally, I had to balance my time between my day job as a software engineer and this side project, making time management a critical aspect of the endeavor.&lt;/p&gt;
&lt;p&gt;Another significant difficulty was the proofreading process. I needed both technical and language reviews to ensure the content was accurate and well-written, considering English is not my native language. Finding reliable reviewers who could provide timely and constructive feedback was challenging. Despite reaching out to many contacts, only a fraction responded and contributed consistently.&lt;/p&gt;
&lt;p&gt;Self-publishing also taught me &lt;em&gt;marketing&lt;/em&gt;, one of the best skills I could have learned, and I’m still leveraging it to this day while working on &lt;a href=&quot;https://mergify.com&quot;&gt;Mergify&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Scaling Python and Serious Python&lt;/h2&gt;
&lt;p&gt;Following the success of my first book, I continued exploring new topics and challenges within the Python ecosystem. &lt;em&gt;&lt;a href=&quot;http://scaling-python.com&quot;&gt;Scaling Python&lt;/a&gt;&lt;/em&gt;, published in 2017, delved into the complexities of scaling applications, a topic that resonated with many developers facing similar challenges. I distributed around 1,000 copies of this book.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/86b0bfff-9015-4bf6-a5d6-a027cbc6f98a_329x459.png&quot; alt=&quot;Cover of Scaling Python&quot; /&gt;
&lt;em&gt;Scaling Python cover&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In 2019, after being approached by &lt;a href=&quot;https://nostarch.com/&quot;&gt;No Starch&lt;/a&gt;, I released &lt;em&gt;&lt;a href=&quot;https://serious-python.com&quot;&gt;Serious Python&lt;/a&gt;&lt;/em&gt;, a book aimed at helping developers write more efficient, maintainable, and scalable code. Both books received praise for their practical approach and in-depth coverage of advanced topics. Being backed by No Starch helped the book to be distributed widely, and made it reach &lt;strong&gt;20,000 copies&lt;/strong&gt; as of today.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/1ef41626-ec9b-4637-9e5d-2962a2ce27b6_2284x2284.jpeg&quot; alt=&quot;Cover of Serious Python published by No Starch Press&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Impact on My Career&lt;/h2&gt;
&lt;p&gt;Writing these books significantly impacted my career and established me as an authority in the Python community. When I joined &lt;a href=&quot;https://datadoghq.com&quot;&gt;Datadog&lt;/a&gt; in 2019, I remember seeing my books casually lying around at the entrance of the Paris office.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/3664e543-72d2-4659-92e5-84d7328a74df_3456x3492.jpeg&quot; alt=&quot;Julien&apos;s books displayed at the Datadog Paris office entrance in 2019&quot; /&gt;
&lt;em&gt;My books chilling in the Datadog Paris office entrance in 2019&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This moment was a profound realization of the reach and influence of my work. Colleagues and peers often treated the content of my books as definitive guides. The books provided answers and insights so clearly that people often didn&apos;t feel the need to ask me questions about the topics I covered during interviews; they trusted my written word as a reliable source. This validation opened new opportunities and allowed me to connect with an extensive network of professionals who recognized and respected my expertise.&lt;/p&gt;
&lt;h2&gt;Connecting with the Community&lt;/h2&gt;
&lt;p&gt;Writing allowed me to talk to anyone, reach out to amazing hackers worldwide, and forge new friendships. Writing books was the best excuse to meet fantastic people and create new friendships. I discovered fantastic engineers and learned from their experiences while interviewing them. This journey has been incredibly rewarding, not just professionally but also personally, as I connected with a vibrant community of developers who share my passion for Python and open-source software.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/07db89c4-6430-4a4c-b10b-61a84baff249_595x595.jpeg&quot; alt=&quot;Julien presenting his book on stage at PyCon FR 2017&quot; /&gt;
&lt;em&gt;Sharing the knowledge of my book on stage during PyCon FR 2017&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I also saw my books being translated into multiple languages, including Chinese and Korean.&lt;/p&gt;
&lt;p&gt;This feeling is awesome as it gives even more impact to your writing, knowing that your knowledge is spreading across the globe. Having your work translated and accessible to a wider audience is a great reward, and it emphasizes the importance and value of sharing knowledge on such a large scale.&lt;/p&gt;
&lt;h2&gt;The Joy of Writing&lt;/h2&gt;
&lt;p&gt;While writing is hard, it is also refreshing. Producing content that people love and are happy to recommend is a fantastic feeling. My golden rule was, and still is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Produce content that you&apos;d be happy consuming.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The rest then becomes history. This philosophy guided me through the writing process and ensured that my books remained valuable and relevant to readers.&lt;/p&gt;
&lt;h2&gt;A New Era of Writing&lt;/h2&gt;
&lt;p&gt;Reflecting on my writing journey, it resonates deeply with a post I wrote titled &quot;&lt;em&gt;&lt;a href=&quot;https://julien.danjou.info/p/i-used-to-write&quot;&gt;I used to write&lt;/a&gt;.&lt;/em&gt;” In that post, I shared my journey from writing extensively in my early years to facing the challenges of balancing life and work, decreasing my writing output. The desire to return to the keyboard lingered, and despite the rise of AI-generated content, I realized that authentic, human writing still holds immense value.&lt;/p&gt;
&lt;p&gt;Over the last year, I toyed with GPT, generating tons of content and using it to brainstorm, change sentences, and rewrite text. This experimentation reaffirmed my belief that AI could never truly replace the nuanced and creative process of human writing. As AI-generated content grows, the need for genuine, human-crafted writing becomes even more critical. This new writing era challenges us to strengthen our signal amidst the growing noise.&lt;/p&gt;
&lt;p&gt;The past ten years have been an incredible journey of learning, teaching, and connecting with developers worldwide. I look forward to continuing this journey, exploring new topics, and sharing my insights through future books and blog posts.&lt;/p&gt;
&lt;p&gt;Thank you for being a part of this journey.&lt;/p&gt;
</content:encoded></item><item><title>The Biggest Mistake We Made Building Mergify: Navigating the Payment System Nightmare</title><link>https://julien.danjou.info/blog/the-biggest-mistake-we-made-building/</link><guid isPermaLink="true">https://julien.danjou.info/blog/the-biggest-mistake-we-made-building/</guid><description>Navigating the Pitfalls of Payment Processing: Lessons Learned from Integrating Stripe, GitHub Marketplace, and Paddle</description><pubDate>Tue, 11 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In 2018, we embarked on an exciting journey with &lt;a href=&quot;https://mergify.com&quot;&gt;Mergify&lt;/a&gt;, our brainchild aimed at simplifying GitHub pull request workflows. One of the first crucial decisions we faced was choosing a payment processor. &lt;a href=&quot;https://stripe.com&quot;&gt;Stripe&lt;/a&gt;, with its developer-centric approach, seemed like the perfect fit. Within a few days, I had mastered the Stripe API and built the foundational billing system for Mergify. For a while, everything ran smoothly as we scaled our user base. However, we soon encountered a significant roadblock: handling VAT in Europe.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/350e34cf-0664-400b-a8ff-880cc58f2776_1500x1000.jpeg&quot; alt=&quot;Illustration of navigating payment system challenges&quot; /&gt;&lt;/p&gt;
&lt;p&gt;European VAT is notoriously complex, with countless edge cases that can quickly become a nightmare for any business. Invoicing internationally from France presented additional challenges, leading us to the conclusion that outsourcing our invoicing would be the best course of action.&lt;/p&gt;
&lt;h2&gt;The GitHub Marketplace Misstep&lt;/h2&gt;
&lt;p&gt;In 2019, the &lt;a href=&quot;https://github.com/marketplace&quot;&gt;GitHub Marketplace&lt;/a&gt; appeared to be an attractive solution. It promised to streamline invoicing while exposing Mergify to a broader audience. Although GitHub took a 15% cut (later reduced to 5%), we were not focused on optimizing margins at that stage.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/87f22244-5403-401f-b6b6-07e2bd2c2ad9_3000x1610.webp&quot; alt=&quot;Screenshot of the GitHub Marketplace&quot; /&gt;
&lt;em&gt;GitHub Marketplace&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;However, this decision turned out to be a colossal mistake.&lt;/p&gt;
&lt;p&gt;Issues with payments began to surface almost immediately. Problems with GitHub’s payment system meant we often had to ask customers to contact GitHub support, creating a frustrating experience for them. Our inability to manage payments directly, such as retrying failed transactions, was a significant drawback. It became evident that while the GitHub Marketplace was a great tool for acquiring new customers, it was far from ideal for handling payments.&lt;/p&gt;
&lt;p&gt;As a glaring example, if a GitHub customer switched from credit card billing to invoice, they would lose access to all marketplace apps, including Mergify. This could abruptly cut off our service, leading to dissatisfied customers and lost revenue. By 2020, we decided to completely transition away from the GitHub Marketplace for payments, migrating our customers back to Stripe. This move eliminated the invoicing problems caused by GitHub and allowed us to regain control over our billing process.&lt;/p&gt;
&lt;h2&gt;The Paddle Predicament&lt;/h2&gt;
&lt;p&gt;Despite our move back to Stripe, the VAT issue remained unresolved. In our quest for a better solution, we discovered &lt;a href=&quot;https://www.paddle.com/&quot;&gt;Paddle&lt;/a&gt;, a platform that promised to handle VAT by becoming the merchant of record for our transactions. We quickly integrated Paddle into our system, hopeful it would be the solution we needed. Unfortunately, this decision soon proved to be another costly mistake.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/20387ab7-572e-41a4-b9f8-1122f386de90_1186x779.png&quot; alt=&quot;Screenshot of the Paddle payment platform&quot; /&gt;
&lt;em&gt;Paddle&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Paddle&apos;s API was far less sophisticated than Stripe’s, and we found ourselves grappling with numerous limitations and workarounds to integrate our billing system. The added complexity and subpar user experience led us to conclude that Paddle was not the right fit for Mergify.&lt;/p&gt;
&lt;h2&gt;The Turning Point: Handling VAT Ourselves&lt;/h2&gt;
&lt;p&gt;Realizing that there was no perfect third-party solution, we decided to tackle the VAT problem head-on. In 2020, we took the plunge and developed our own VAT handling system using Stripe and Python. We detailed this process in a &lt;a href=&quot;https://blog.mergify.com/handling-european-vat-with-stripe/&quot;&gt;blog post&lt;/a&gt; sharing our approach and challenges.&lt;/p&gt;
&lt;p&gt;Fortunately, Stripe was also working on solving the VAT issue. By the end of 2021, they released their &lt;a href=&quot;https://stripe.com/en-fr/tax&quot;&gt;comprehensive tax product&lt;/a&gt;, simplifying VAT and other tax processes. This allowed us to finally switch fully to Stripe, discarding our custom code in favor of their robust solution.&lt;/p&gt;
&lt;h2&gt;Lessons Learned&lt;/h2&gt;
&lt;p&gt;The most significant lesson from our journey is that payment processing is not a mere detail—it’s an integral part of the user experience. Even now, I spend several hours each month resolving payment issues, from credit card problems to ensuring invoices are correctly fed into various supplier systems. While automation can handle many aspects, the unique methods and systems of each customer often require personalized solutions.&lt;/p&gt;
&lt;p&gt;Our experience underscores the importance of keeping things simple on your side and minimizing friction for your customers. Ensuring a smooth and reliable payment process is crucial for maintaining customer satisfaction and loyalty.&lt;/p&gt;
&lt;p&gt;In hindsight, we should have approached the payment system with the same rigor and attention to detail as the rest of our product.&lt;/p&gt;
&lt;p&gt;It&apos;s a lesson we learned the hard way, but one that has ultimately strengthened Mergify and our commitment to providing the best possible service for our users.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This is part 1 of the &quot;Biggest Mistakes&quot; series. Read part 2: &lt;a href=&quot;https://julien.danjou.info/blog/the-biggest-mistake-we-made-building-a43&quot;&gt;Navigating the Hiring Minefield&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Sponsoring Conferences</title><link>https://julien.danjou.info/blog/sponsoring-conferences/</link><guid isPermaLink="true">https://julien.danjou.info/blog/sponsoring-conferences/</guid><description>Our experience at Mergify with conference sponsoring.</description><pubDate>Thu, 06 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last week, &lt;a href=&quot;https://julien.danjou.info/p/attending-conferences&quot;&gt;I wrote about my experience attending conferences&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Over the last year, we&apos;ve tried to expose Mergify at conferences to reach out to developers. We’ve done various conferences in Europe and the US—the largest being &lt;a href=&quot;https://qconsf.com/&quot;&gt;QCon San Francisco&lt;/a&gt; 2023 and &lt;a href=&quot;https://www.devoxx.fr/&quot;&gt;Devoxx France&lt;/a&gt; 2024. We sponsored those events and ran booths for several days all day long to talk to engineers.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/b4e4296f-966f-43cc-b434-4b6668db7706_1572x1893.webp&quot; alt=&quot;Mergify booth at QCon San Francisco 2023&quot; /&gt;
&lt;em&gt;Mergify booth at QCon San Francisco 2023&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The pattern we’ve seen has been interesting. First, QCon San Francisco was the smallest it’s been over the last few years, as far as I can tell. While 1,400 people were expected, counting the number of people seated in the keynote session revealed less than half of that were present. We talked to tens of engineers without great success. It turns out that trying to sell your tool for an early startup like Mergify is not efficient at all in such a place. Companies tend to do that when they are way larger to raise brand awareness and penetrate the market more efficiently.&lt;/p&gt;
&lt;p&gt;At our stage, this was a lot of money spent for barely any gain.&lt;/p&gt;
&lt;p&gt;As Mehdi, my cofounder and CTO says, “no great engineer will go to a conference to find the next tool they’ll need.” Indeed, I don’t believe any good engineer should wait 6 months for the next conference they will attend to find a product to their technical problems.&lt;/p&gt;
&lt;p&gt;Doing market education, as we tried, over a booth, is utopian. Here’s the typical dialogue that would happen:&lt;/p&gt;
&lt;p&gt;– &lt;em&gt;Engineer attending the conference:&lt;/em&gt; “Hi! What does Mergify do?”&lt;br /&gt;
– &lt;em&gt;Mergify staff:&lt;/em&gt; “We offer merge queues for your GitHub repository. Do you know about them?”&lt;br /&gt;
– &lt;em&gt;Engineer attending the conference trying not to lose face:&lt;/em&gt; “Yeah, for sure!”&lt;br /&gt;
– &lt;em&gt;Mergify staff:&lt;/em&gt; “Do you use one in your team?”&lt;br /&gt;
– &lt;em&gt;Engineer:&lt;/em&gt; “No, we don’t need one.”&lt;br /&gt;
– &lt;em&gt;Mergify:&lt;/em&gt; &quot;How so? You&apos;re happy merging outdated PR or running a lot of CI on every PR?&quot;&lt;br /&gt;
– &lt;em&gt;Engineer:&lt;/em&gt; &quot;We… don&apos;t… well… err… what are we talking about exactly?…&quot;&lt;/p&gt;
&lt;p&gt;The truth is, 95% of the engineers we talked to have no clue what a merge queue is. Actually, 80% of them don’t know anything about Git besides the basics (i.e., commit and push), and chatting for 10 minutes over a booth is not a good place to educate them.&lt;/p&gt;
&lt;p&gt;Speaking at conferences is a way better strategy, as the advent of the Developer Evangelist role has demonstrated over the last years. If well executed, it’s cheaper and can have a far better outcome than sponsoring an event.&lt;/p&gt;
&lt;p&gt;You could imagine that sponsoring an event buys you a ticket to speak, but it’s not the case by default. Some conferences allow you to buy speaking time in special, dedicated rooms, for example, but you usually don’t get any special treatment over the regular CfP.&lt;/p&gt;
&lt;p&gt;I really need to talk about that CfP game.&lt;/p&gt;
</content:encoded></item><item><title>Attending Conferences</title><link>https://julien.danjou.info/blog/attending-conferences/</link><guid isPermaLink="true">https://julien.danjou.info/blog/attending-conferences/</guid><description>How my conferences attendance shifted over the last decade.</description><pubDate>Tue, 04 Jun 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;There’s a lot to say about tech conferences, shows, and exhibits in any form. I’ve been to a few of them over the last decade, and I probably can&apos;t count anymore at this point.&lt;/p&gt;
&lt;p&gt;I thought it’d be interesting to write a thing or two about them and how my experience changed from my first participation in a gathering of tech people to the most recent one.&lt;/p&gt;
&lt;h2&gt;Goals&lt;/h2&gt;
&lt;p&gt;What especially changed for me over the last 15 years is the expectation of conferences.&lt;/p&gt;
&lt;p&gt;I remember that the first conferences I went to appeared especially exciting because of the &lt;strong&gt;content&lt;/strong&gt;. I was utterly interested in hearing about new technologies, discovering new features, and learning new practices. My goal as a young software engineer was to become the best at my craft, so I’d go there and attend as many lectures as I could.&lt;/p&gt;
&lt;p&gt;Sometimes, that would be very challenging. Take the &lt;a href=&quot;https://fosdem.org&quot;&gt;FOSDEM&lt;/a&gt;, one of the largest open-source conferences in Europe, with thousands of geeks attending. I attended FOSDEM numerous times. The buildings they use to run the conference have been the same all along those years, and are known to be way too small to welcome many people to most conferences. There are fantastic talks done by some of the world&apos;s greatest hackers, but there are more people waiting in the hall to access the talk than people in the room listening to it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/2648c606-59ca-40bd-88ab-19b0235d0518_1280x720.jpeg&quot; alt=&quot;Photo of a packed FOSDEM conference hallway with attendees waiting to enter&quot; /&gt;
&lt;em&gt;Regular FOSDEM attendance&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You could think that this would ruin the conference for most people, but I think it does not. It shows the other very important aspect of conferences: social &lt;strong&gt;interaction&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I know there are so many stereotypes about tech guys not interacting with each other. The truth is, as human beings, we crave social connections, and events are a great source of those. Attending my first OpenStack Summit in 2013 allowed me to meet people I only chatted with over IRC, and it was a real game changer later on.&lt;/p&gt;
&lt;p&gt;As my engineering career grew, there was not much interesting to learn from the talks. Many lectures became boring or déjà-vu.&lt;/p&gt;
&lt;p&gt;I quickly switched sides and became the one giving talks and sharing knowledge. This was great. It gave me a great sense of recognition, validation, fear, and adrenaline. It’s a significant boost for anyone’s career. It was a game changer for me.&lt;/p&gt;
&lt;h2&gt;COVID&lt;/h2&gt;
&lt;p&gt;When COVID happened, everything changed.&lt;/p&gt;
&lt;p&gt;I remember receiving a phone call in early March 2020 from Sylvain Zimmer, organizer of the dotConferences. I was booked to speak at &lt;a href=&quot;https://www.dotpy.io/&quot;&gt;dotPy&lt;/a&gt;, and my talk on Python performances and profiling was ready to go in a couple of days. I would be live on stage in a Parisian theater in front of hundreds of people. Sylvain explained that something was happening, that they couldn’t risk having this event run, and that they had to cancel.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/172ab082-db07-4667-9eec-82640b8ef8c6_1200x546.jpeg&quot; alt=&quot;Photo of an empty conference venue during COVID-19 cancellations&quot; /&gt;&lt;/p&gt;
&lt;p&gt;For the next months, every event was canceled, and people shifted to online, remote work, etc. — you know it all. This broke local communities and the habit of many people going to conferences.&lt;/p&gt;
&lt;p&gt;I think this shows overall as there are fewer people going to events today than there used to be. People got used to accessing content over the Internet, webinars bloomed, and since many conferences published their lectures online, interest in traveling to a conference reduced a lot.&lt;/p&gt;
&lt;p&gt;I relaunched the &lt;a href=&quot;https://www.meetup.com/fr-FR/python-toulouse/&quot;&gt;Python Toulouse meetup&lt;/a&gt; group 18 months ago. There were more than 800 members on that group when we announced that we were scheduling a new session in October 2022—3 years after the last one.&lt;/p&gt;
&lt;p&gt;We got only 5 attendees.&lt;/p&gt;
&lt;p&gt;Since then, we have continued pushing the event every couple of months, and the event has grown back to more than 40 attendees (and I have stepped down from the organizers).&lt;/p&gt;
&lt;p&gt;I think this shows well how bad the COVID hit conferences and meetups in general.&lt;/p&gt;
&lt;h2&gt;Attending Conferences&lt;/h2&gt;
&lt;p&gt;As I started running &lt;a href=&quot;https://mergify.com&quot;&gt;Mergify&lt;/a&gt; a few years ago, my expectations of conferences shifted again. As we are building a developer tool, so the developer is now a persona we want to reach to make us aware of what we’re building.&lt;/p&gt;
&lt;p&gt;There are two ways of doing that, and most developer-focused companies do one or both:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;speak at conferences;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;expose at conferences (sponsoring).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I’ll write something about event sponsoring some other time. (Update: &lt;a href=&quot;https://julien.danjou.info/blog/sponsoring-conferences&quot;&gt;I did&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;Winning a slot to speak at conferences is not easy: it requires expertise (we have) and time and focus (we don’t have much). In my case, we encourage folks at Mergify to respond to calls for papers, teach other developers the problems we solve, or share our experience on various topics. This is not always working; unfortunately, we are not experts at playing the CfP game—another topic I should write about.&lt;/p&gt;
&lt;p&gt;First, I noticed that while there are more and more software engineers, many of them don’t care about going to conferences. They know most of the talks are already online or will be. As the CfP game is getting professional, many talks you see at conferences have already been lectured, filmed, and published online. Engineers valuing their time might not go to conferences in the end.&lt;/p&gt;
&lt;p&gt;Some conferences are trying to fix that by not publishing their talks online. I think it can be a good strategy in certain cases, but as many conferences invite speakers with talks running for months if not years, it’s likely you can already watch the content online anyway.&lt;/p&gt;
&lt;p&gt;Second, many events and conferences still overpromise the number of people actually attending. It’s likely the pre-COVID level is not back everywhere.&lt;/p&gt;
&lt;p&gt;Last, the average technical level of expertise of both speakers and attendees fluctuates a lot. However, the more I think about it, the less I see a pattern. Some community-run conferences have great and poor content simultaneously; some professional conferences have attendees with low-skill but great speakers. It’s hard to have a rule, and I think it’s really on a case-by-case basis—and it might be subjective, after all.&lt;/p&gt;
&lt;p&gt;Those issues might be anecdotic for most, though they’re not for me as they explain partly why Mergify&apos;s sponsoring of events has been mostly a failure over the last year.&lt;/p&gt;
&lt;p&gt;But I’ll talk about that later.&lt;/p&gt;
</content:encoded></item><item><title>I used to write</title><link>https://julien.danjou.info/blog/i-used-to-write/</link><guid isPermaLink="true">https://julien.danjou.info/blog/i-used-to-write/</guid><description>Then I stopped.</description><pubDate>Tue, 21 May 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I did. I mean, I used to write a lot back in the day.&lt;/p&gt;
&lt;p&gt;To give some context, I started my blog in 2003 — 21 years ago. (I later reflected on my writing career in &lt;a href=&quot;https://julien.danjou.info/blog/a-decade-of-writing-books-and-selling&quot;&gt;A Decade of Writing Books and Selling 25,000 Copies&lt;/a&gt;.) It ran on &lt;a href=&quot;https://dotclear.org/&quot;&gt;Dotclear&lt;/a&gt;, an old blog engine. I switched to various static code generators over the years, spending hours migrating data from one format to another. I used &lt;a href=&quot;https://www.gnu.org/software/emacs-muse/&quot;&gt;Muse&lt;/a&gt; within Emacs, &lt;a href=&quot;https://orgmode.org/worg/org-blog-wiki.html&quot;&gt;Org-mode&lt;/a&gt;, probably at some point, and finally, &lt;a href=&quot;https://ghost.org/&quot;&gt;Ghost&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The cover of Serious Python&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Then I stopped.&lt;/p&gt;
&lt;p&gt;The number of publications I posted online decreased over the years as I spent more and more time coding, CEO’ing things at Mergify, and managing life. Writing slowly faded away, replaced with the mundane demandes of daily life. I closed my blog, and its content vanished in the limb of a file named &lt;code&gt;jd-dev-blog.zip&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/3e3c41c1-0e3d-4cdd-ac9d-593b43e453db_1666x116.png&quot; alt=&quot;Graph showing declining blog post frequency over the years&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Sure, I thought about writing more. Or again. Over the last couple of years, every week, my stomach would ache, and my brain would melt under the weight of my thoughts. I wanted to yell so many things at the world, correct so many wrongs, and share so many learnings.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/images/blog/68c373a9-7d09-4f9c-a0b6-3b8943bb16c2_300x330.png&quot; alt=&quot;XKCD comic about someone being wrong on the internet&quot; /&gt;
&lt;em&gt;https://xkcd.com/386/&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;But the time to write disappeared, buried under the many priorities that other necessities. Still, the desire to return to the keyboard lingered.&lt;/p&gt;
&lt;p&gt;Until the final nail in the coffin hit.&lt;/p&gt;
&lt;p&gt;AI became mainstream.&lt;/p&gt;
&lt;p&gt;Anyone could write anything in seconds. Copywriters were being replaced by bots. The cost savings of replacing humans would revolutionize entire industries. There was no point in writing anymore. My engineer brain decided that the problem was solved.&lt;/p&gt;
&lt;p&gt;So I gave up. My mind gave up.&lt;/p&gt;
&lt;p&gt;My desire to write would be instantly killed by the concept of ChatGPT. The existence of that malicious AI would stifle any urge to draft thoughts on virtual paper. The sheer thought of such an entity lurking in the digital shadows, analyzing and predicting every word, strikes a chilling fear into the heart of my creativity.&lt;/p&gt;
&lt;p&gt;Until today.&lt;/p&gt;
&lt;p&gt;Over the last year, I toyed with GPT, generating tons of content. I used it to brainstorm, change sentences, and rewrite text. The more I used it, the more I realized I was getting bored. Browsing the Internet, and social networks, I realized humans were replaced by AI.&lt;/p&gt;
&lt;p&gt;No one would &lt;em&gt;write&lt;/em&gt; anymore; everyone would just &lt;em&gt;publish&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;With its cold, calculating algorithms, AI reduced the rich tapestry of human expression to mere patterns and data points. People would throw a bare concept or even ask AI for one and demand it to produce text. Many of those publishers would not even take the time to tweak the AI, to feed it with the small amount of style and humanity they would have. Content would be farmed, from social media posts to SEO blog posts.&lt;/p&gt;
&lt;p&gt;Over the last year, everything became bland. The once vibrant landscape of ideas has been replaced with mechanical mimicry.&lt;/p&gt;
&lt;p&gt;My brain acknowledged that AI could never truly write. That revelation shifted my perspective, and I realized that writing wasn&apos;t dead. The noise would undoubtedly grow louder, but this only meant the signal would need to be stronger.&lt;/p&gt;
&lt;p&gt;We are entering a new era for writing.&lt;/p&gt;
&lt;p&gt;Well, at least, that’s what I hope.&lt;/p&gt;
</content:encoded></item><item><title>Debugging C code on macOS</title><link>https://julien.danjou.info/blog/debugging-c-code-on-macoss/</link><guid isPermaLink="true">https://julien.danjou.info/blog/debugging-c-code-on-macoss/</guid><description>I started to write C 25 years ago now, with many different tools over the year. As many open source developers, I spent most of my life working with the GNU tools out there.  As I&apos;ve been using an App</description><pubDate>Thu, 11 Feb 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I started to write C 25 years ago now, with many different tools over the year. As many open source developers, I spent most of my life working with the GNU tools out there.&lt;/p&gt;
&lt;p&gt;As I&apos;ve been using an Apple computer over the last years, I had to adapt to this environment and learn the tricks of the trade. Here are some of my notes so a search engine can index them — and I&apos;ll be able to find them later.&lt;/p&gt;
&lt;h2&gt;Debugger: lldb&lt;/h2&gt;
&lt;p&gt;I was used to `gdb` for most of years doing C. I never managed to install gdb correctly on macOS as it needs certificates, authorization, you name it, to work properly.&lt;/p&gt;
&lt;p&gt;macOS provides a native debugger named lldb, which really looks like gdb to me — it runs in a terminal with a prompt.&lt;/p&gt;
&lt;p&gt;I had to learn the few commands I mostly use, which are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;lldb -- myprogram -options&lt;/code&gt; to run the program with options&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r&lt;/code&gt; to run the program&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bt&lt;/code&gt; or &lt;code&gt;bt N&lt;/code&gt; to get a backtrace of the latest N frames&lt;/li&gt;
&lt;li&gt;&lt;code&gt;f N&lt;/code&gt; to select frame N&lt;/li&gt;
&lt;li&gt;&lt;code&gt;p V&lt;/code&gt; to print some variable value or memory address&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those commands cover 99% of my use case with a debugger when writing C, so once I lost my old &lt;code&gt;gdb&lt;/code&gt; habits, I was good to go.&lt;/p&gt;
&lt;h2&gt;Debugging Memory Overflows&lt;/h2&gt;
&lt;h3&gt;On GNU/Linux&lt;/h3&gt;
&lt;p&gt;One of my favorite tools when writing C has always been &lt;a href=&quot;https://en.wikipedia.org/wiki/Electric_Fence&quot;&gt;Electric Fence&lt;/a&gt; (and &lt;a href=&quot;http://duma.sourceforge.net/&quot;&gt;DUMA&lt;/a&gt; more recently). It&apos;s a library that overrides the standard memory manipulation function (e.g., &lt;code&gt;malloc&lt;/code&gt;) and instantly makes the program crash when an out of memory error is produced, rather than corrupting the heap.&lt;/p&gt;
&lt;p&gt;Heap corruption issues are hard to debug without such tools as they can happen at any time and stay unnoticed for a while, crashing your program in a totally different location later.&lt;/p&gt;
&lt;p&gt;There&apos;s no need to compile your program with those libraries. By using the dynamic loader, you can preload them and overload the standard C library functions.&lt;/p&gt;
&lt;p&gt;My &lt;code&gt;gdb&lt;/code&gt; configuration has been sprinkle with my friends &lt;em&gt;efence&lt;/em&gt; and &lt;em&gt;duma&lt;/em&gt;, and I would activate them from &lt;code&gt;gdb&lt;/code&gt; easily with this configuration in &lt;code&gt;~/.gdbinit&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;define efence
        set environment EF_PROTECT_BELOW 0
        set environment EF_ALLOW_MALLOC_0 1
        set environment LD_PRELOAD /usr/lib/libefence.so.0.0
        echo Enabled Electric Fence\n
end
document efence
Enable memory allocation debugging through Electric Fence (efence(3)).
        See also nofence and underfence.
end

define underfence
        set environment EF_PROTECT_BELOW 1
        set environment EF_ALLOW_MALLOC_0 1
        set environment LD_PRELOAD /usr/lib/libefence.so.0.0
        echo Enabled Electric Fence for underflow detection\n
end
document underfence
Enable memory allocation debugging for underflows through Electric Fence
(efence(3)).
        See also nofence and efence.
end

define nofence
        unset environment LD_PRELOAD
        echo Disabled Electric Fence\n
end
document nofence
Disable memory allocation debugging through Electric Fence (efence(3)).
end

define duma
        set environment DUMA_PROTECT_BELOW 0
        set environment DYMA_ALLOW_MALLOC_0 1
        set environment LD_PRELOAD /usr/lib/libduma.so
        echo Enabled DUMA\n
end
document duma
Enable memory allocation debugging through DUMA (duma(3)).
        See also noduma and underduma.
end
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;On macOS&lt;/h3&gt;
&lt;p&gt;I&apos;ve been looking for equivalent features in macOS, and after many hours of research, I found out that this feature is shipped natively with &lt;code&gt;libgmalloc&lt;/code&gt;. It works in the same way, and &lt;a href=&quot;https://developer.apple.com/library/archive/documentation/Performance/Conceptual/ManagingMemory/Articles/MallocDebug.html&quot;&gt;its features are documented by Apple&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;My &lt;code&gt;~/.lldbinit&lt;/code&gt;  file now contains the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;command alias gm _regexp-env DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command alias allows enabling &lt;code&gt;gmalloc&lt;/code&gt; by just typing &lt;code&gt;gm&lt;/code&gt; at the lldb prompt and then &lt;code&gt;run&lt;/code&gt; the program again to see if it crashes with &lt;code&gt;gmalloc&lt;/code&gt; enabled.&lt;/p&gt;
&lt;h2&gt;Debugging CPython&lt;/h2&gt;
&lt;p&gt;It&apos;s not a mystery that I spend a lot of time writing Python code — that&apos;s the main reason I&apos;ve been doing C lately.&lt;/p&gt;
&lt;p&gt;When playing with CPython, it can be useful to, e.g., dump the content of &lt;code&gt;PyObject&lt;/code&gt; structs on the heap or get the Python backtrace.&lt;/p&gt;
&lt;p&gt;I&apos;ve been using &lt;a href=&quot;https://github.com/malor/cpython-lldb&quot;&gt;&lt;em&gt;cpython-lldb&lt;/em&gt;&lt;/a&gt; for this with great success. It adds a few bells and whistles when debugging CPython or extensions inside &lt;code&gt;lldb&lt;/code&gt;. For example, the alias &lt;code&gt;py-bt&lt;/code&gt; is handy to get the Python traceback of your calls rather than a bunch of cryptic C frames.&lt;/p&gt;
&lt;p&gt;Now, you should be ready to debug your nasty issues and memory problems on macOS efficiently!&lt;/p&gt;
</content:encoded></item><item><title>I am a Software Engineer and I am in Charge</title><link>https://julien.danjou.info/blog/i-am-a-software-engineer-and-i-am-in-charge/</link><guid isPermaLink="true">https://julien.danjou.info/blog/i-am-a-software-engineer-and-i-am-in-charge/</guid><description>Fifteen years have passed since I started my career in IT — which is quite some time. I&apos;ve been playing with computers for 25 years now, which makes me quite knowledgeable about the field, for sure.</description><pubDate>Tue, 22 Dec 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Fifteen years have passed since I started my career in IT — which is quite some time. I&apos;ve been playing with computers for 25 years now, which makes me quite knowledgeable about the field, for sure.&lt;/p&gt;
&lt;p&gt;However, while I was fully prepared to bargain with computers, I was not prepared to do so with humans. The whole career management thing was unknown to me. I had no useful skills to navigate within the enterprise organization. I had to learn the ropes the hard way, failing along the way. It hurts.&lt;/p&gt;
&lt;p&gt;Almost ten years ago, I had the chance to meet a new colleague — Alexis Monville. Alexis was a team facilitator, and I started to work with him on many non-technical levels. He taught me a lot about agility and team organization. Working on this set of new skills changed how I envisioned my work and how I fit into the company.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/12/alexis-monville-1.png&quot; alt=&quot;Alexis Monville&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I worked on those aspects of my job because I decided to be in charge of my career rather than keeping things boring. That was one of the best decisions I ever made. Growing the social aspect of my profession allowed me to develop and find aspiring jobs and missions.&lt;/p&gt;
&lt;p&gt;Getting to that point takes a lot of time and effort, and it&apos;s pretty hard to do it alone. My friend Alexis wrote an excellent book titled &lt;em&gt;&lt;a href=&quot;https://leanpub.com/iamincharge/c/jd-affiliate&quot;&gt;I am a Software Engineer and I am in Charge&lt;/a&gt;.&lt;/em&gt; I&apos;m proud to have been the &lt;a href=&quot;https://iamincharge.club/2020/04/16/the-first-review/&quot;&gt;first reviewer&lt;/a&gt; the book before it was released a few weeks ago.&lt;/p&gt;
&lt;p&gt;Many developers out there are stuck in a place where they are not excited by their colleagues&apos; work and whose managers do not appropriately recognize their achievement. It would be best for them if they did something about that.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/12/image.png&quot; alt=&quot;The book!&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This book is an excellent piece for engineers who wants to break the cycle of frustration. It covers many situations I encountered across my professional life those last years, giving good insights into how to solve them.&lt;/p&gt;
&lt;p&gt;To paraphrase Alexis, the answers to your career management problems are not on StackOverflow — they&apos;re not technical issues. However, you can still solve them with the right tools. That&apos;s where &lt;em&gt;&lt;a href=&quot;https://leanpub.com/iamincharge/c/jd-affiliate&quot;&gt;I am a Software Engineer and I am in Charge&lt;/a&gt;&lt;/em&gt; shines. It gives you leads, solutions, and exercise to get out of this kind of situation. It helps increase your impact and satisfaction at work.&lt;/p&gt;
&lt;p&gt;I love this book, and I wish I had access to it years ago. Developing technical leadership is not easy and requires a mindset shift. Having a way to bootstrap yourself with this is a luxury.&lt;/p&gt;
&lt;p&gt;If you&apos;re a software engineer at the beginning of your career or struggling with your current professional situation, I profoundly recommend reading &lt;a href=&quot;https://leanpub.com/iamincharge/c/jd-affiliate&quot;&gt;this book&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;You&apos;ll get a fast track on your career, for sure.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/12/cropped-Logo-1-1.png&quot; alt=&quot;I Am a Software Engineer and I Am in Charge book logo&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>Interview: The Performance of Python</title><link>https://julien.danjou.info/blog/interview-the-performance-of-python/</link><guid isPermaLink="true">https://julien.danjou.info/blog/interview-the-performance-of-python/</guid><description>Earlier this year, I was supposed to participate to dotPy, a one-day Python conference happening in Paris. This event has unfortunately been cancelled due to the COVID-19 pandemic.</description><pubDate>Mon, 11 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Earlier this year, I was supposed to participate to &lt;a href=&quot;https://dotpy.io&quot;&gt;dotPy&lt;/a&gt;, a one-day Python conference happening in Paris. This event has unfortunately been cancelled due to the COVID-19 pandemic.&lt;/p&gt;
&lt;p&gt;Both Victor Stinner and me were supposed to attend that event. Victor had prepared a presentation about Python performances, while I was planning on talking about profiling.&lt;/p&gt;
&lt;p&gt;Rather than being completely discouraged, Victor and I sat down (remotely) with Anne Laure from &lt;a href=&quot;https://www.welcometothejungle.com/en/collections/behind-the-code&quot;&gt;Behind the Code&lt;/a&gt; (a blog ran by Welcome to the Jungle, the organizers of the &lt;a href=&quot;https://dotpy.io&quot;&gt;dotPy&lt;/a&gt; conference).&lt;/p&gt;
&lt;p&gt;We discuss Python performance, profiling, speed, projects, problems, analysis, optimization and the GIL.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.welcometothejungle.com/en/articles/btc-performance-python&quot;&gt;You can read the interview here.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/05/image-5.png&quot; alt=&quot;Screenshot of the Behind the Code interview about Python performance&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>Being in Charge</title><link>https://julien.danjou.info/blog/being-in-charge-10x-engineers-le-podcast/</link><guid isPermaLink="true">https://julien.danjou.info/blog/being-in-charge-10x-engineers-le-podcast/</guid><description>If you never heard of the 10x engineer myth, it&apos;s a pretty great concept. It boils down to the idea where an engineer could be 10x more efficient than a random engineer. I find this fantastically twis</description><pubDate>Fri, 17 Apr 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you never heard of the 10x engineer myth, it&apos;s a pretty great concept. It boils down to the idea where an engineer could be 10x more efficient than a random engineer. I find this fantastically twisted.&lt;/p&gt;
&lt;p&gt;Last week, I sat and chat with Alexis Monville in &lt;a href=&quot;https://alexis.monville.com/en/category/podcast/&quot;&gt;Le Podcast&lt;/a&gt; — a podcast that equips you to make positive change in your organization. &lt;a href=&quot;https://alexis.monville.com/en/2020/04/10/do-you-want-10x-engineers/&quot;&gt;We talked&lt;/a&gt; about that 10x Engineer myth, and from there we digressed on how to grow your career and handle the different aspects of it.&lt;/p&gt;
&lt;p&gt;This was a very interesting exchange. Alexis is actually going to publish a new book next month (May 2020) entitled &lt;a href=&quot;https://iamincharge.club/&quot;&gt;I am a Software Engineer and I am in charge&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/04/image-1.png&quot; alt=&quot;Cover of I Am a Software Engineer and I Am in Charge&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Lucky me, this week, I had the chance to be able to read the book before everybody else — which means I actually read &lt;em&gt;after&lt;/em&gt; our recording. I understood why Alexis said that a lot of what I was talking about during our podcast resonated with him. I send &lt;a href=&quot;https://iamincharge.club/2020/04/16/the-first-review/&quot;&gt;a detailed review of the book to Alexis and Michael&lt;/a&gt; if you&apos;re curious. I&apos;m definitely recommending this book if you want to stop complaining about your job and start understanding how to pull the strings.&lt;/p&gt;
&lt;p&gt;I wish I had this book available 10 years ago! 😅&lt;/p&gt;
</content:encoded></item><item><title>One year of Mergify</title><link>https://julien.danjou.info/blog/one-year-of-mergify/</link><guid isPermaLink="true">https://julien.danjou.info/blog/one-year-of-mergify/</guid><description>It has been close to a year now that I&apos;ve incorporated my new company, Mergify. I&apos;ve been busy, and I barely wrote anything about it so far.</description><pubDate>Thu, 12 Mar 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It has been close to a year now that I&apos;ve incorporated my new company, &lt;a href=&quot;https://mergify.io&quot;&gt;Mergify&lt;/a&gt;. I&apos;ve been busy, and I barely wrote anything about it so far. Now is an excellent time to take a break and reflect a bit on what happened during those last 12 months.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/export-mergify-logo-title-horizontal.png&quot; alt=&quot;Mergify logo&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;What problem does Mergify solve?&lt;/h2&gt;
&lt;p&gt;Mergify is a powerful automation engine for GitHub pull requests. It allows you to automate everything — and especially merging. You write rules, and it handles the rest.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/Screenshot-2020-03-10-at-11.27.23.png&quot; alt=&quot;Example of rule matching returned in GitHub checks&quot; /&gt;&lt;/p&gt;
&lt;p&gt;For example, let&apos;s say you want your pull request to be merged, e.g., once your CI passes and the pull request has been approved. You just write such a rule, and our engine merges the pull request as soon as it&apos;s ready.&lt;/p&gt;
&lt;p&gt;We also deal with more advanced use cases. For instance, we provide &lt;a href=&quot;https://doc.mergify.io/merge-action.html#strict-merge&quot;&gt;a merge queue&lt;/a&gt; so your pull requests are merged serially and tested by your CI one after another — avoiding any regression in your code.&lt;/p&gt;
&lt;p&gt;Our goal is to make pull request management and automation easy. You can use your bot to trigger a rebase of your pull requests, or a backport to a different branch, just with a single comment.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/Screenshot-2020-03-10-at-11.21.37.png&quot; alt=&quot;Some people like to make bots talk to each other.&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;A New Adventure&lt;/h2&gt;
&lt;p&gt;Mergify is the first company that I ever started. I did run some personal businesses before, created non-profit organizations, built FOSS projects — but I never created a company from scratch, even less with an associate.&lt;/p&gt;
&lt;p&gt;Indeed, I&apos;ve chosen to build the company with my old friend &lt;a href=&quot;https://sileht.net/&quot;&gt;Mehdi&lt;/a&gt;. We&apos;ve known each others for 7 years now, and have worked together all that time on different open-source projects. Having worked with each other for so long has probably been a critical factor in the success of our venture so far.&lt;/p&gt;
&lt;p&gt;I had little experience sharing the founding seats with someone, and tons of reading seemed to indicate that it would be a tough ride. Picking the right business partner(s) can be a hard task. Luckily, after working so much time together, Mehdi and I both know our strengths and weaknesses well enough to be able to circumvent them. 😅&lt;/p&gt;
&lt;p&gt;On the other hand, we both have similar backgrounds as software engineers. That does not help to cover all the hats you need to wear when building a company. Over time, we found arrangements to cover most of those equally between us.&lt;/p&gt;
&lt;p&gt;We don&apos;t have any magical advice to give on this. As in every relationship, communication is the key, and the #1 factor of success.&lt;/p&gt;
&lt;h2&gt;Getting Users&lt;/h2&gt;
&lt;p&gt;I don&apos;t know if we got lucky, but we got users and customers pretty early. We used a few cooperative projects as guinea pigs first, and they were brave enough to try our service and give us feedback. No repository has been harmed during this first phase!&lt;/p&gt;
&lt;p&gt;Then, as soon as we managed to get our application on the &lt;a href=&quot;https://github.com/marketplace/mergify&quot;&gt;GitHub Marketplace&lt;/a&gt;, we saw a steady number of users coming to us.&lt;/p&gt;
&lt;p&gt;This has been fantastic as it allowed us to get feedback rapidly. We set up a form asking users for feedback after they used Mergify for a couple of weeks. What we hear is that users were happy, that the documentation was confusing and that some features were buggy or missing. We planned all of those ideas as our future work in our roadmap, using &lt;a href=&quot;https://medium.com/mergify/how-we-handle-our-roadmap-for-mergify-7e813e24508e&quot;&gt;the principles we described a few months ago&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn-images-1.medium.com/fit/c/152/152/1*8I-HPL0bfoIzGied-dzOvA.png&quot; alt=&quot;If you&apos;re curious, you can read this article.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We tried various strategies to get new users, but so far, organic growth has been our #1 way of onboarding new users. Like many small startups out there, we&apos;re not that good at marketing and executing strategies.&lt;/p&gt;
&lt;p&gt;We provide our service for free for open-source projects We are now powering many organizations, such as Mozilla, Amazon Web Services, Ceph and Fedora.&lt;/p&gt;
&lt;h2&gt;Working with GitHub&lt;/h2&gt;
&lt;p&gt;Working with GitHub has been… complicated. When you build an application for a marketplace, your business is entirely dependent on the platform you develop for — both in terms of features and quality of service.&lt;/p&gt;
&lt;p&gt;In our case, we hit quite many bugs with GitHub. Their support has mostly been fast to answer, but some significant issues are still opened months later. The truth is that the GitHub API could deserve more love and care from GitHub. For example, their GraphQL API is a work in progress for years and miss out many essential features.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/Screenshot-2020-03-05-at-16.38.55.png&quot; alt=&quot;GitHub service status is not always green.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We dealt and still deal with all those issues. It obviously impacts our operations and decreases our overall velocity. We regularly have to find new ways to sidestep GitHub limitations.&lt;/p&gt;
&lt;p&gt;You have no idea how we wished for GitHub to be open-source. The idea of not having access to their code and understand how it works is so frustrating that we publish our &lt;a href=&quot;https://github.com/mergifyio/mergify-engine&quot;&gt;engine&lt;/a&gt; as an open-source project. That allows all of our users to see how it works and even propose enhancements.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/Screenshot-2020-03-11-at-10.39.12.png&quot; alt=&quot;Screenshot of the Mergify open-source engine repository on GitHub&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Automate all the way&lt;/h2&gt;
&lt;p&gt;We&apos;re a tiny startup, and we decided to bootstrap our company. We never took any funding. From the beginning, it has been clear to us that we had to think and act like we had no resources. We&apos;re built around a scarcity mindset. Every decision we make is based on the assumption that we basically are very limited in terms of money and time.&lt;/p&gt;
&lt;p&gt;We basically act like any wrong choice we do could (virtually) kill the company. We only do what is essential, we ship fast, and we automate everything.&lt;/p&gt;
&lt;p&gt;For example, we have built our whole operation about CI/CD systems, and pushing any new fix or feature in production is done in a matter of &lt;em&gt;minutes&lt;/em&gt;. It&apos;s not uncommon for us to push a fix from our phone, just by reviewing some code or editing a file.&lt;/p&gt;
&lt;h2&gt;Growth&lt;/h2&gt;
&lt;p&gt;We&apos;re extremely happy with our steady growth and more users using our service. We now manage close to 30k repositories and merge 15k pull requests per month for our users.&lt;/p&gt;
&lt;p&gt;That&apos;s a lot of mouse clicks saved!&lt;/p&gt;
&lt;p&gt;If you want to try &lt;a href=&quot;https://mergify.io&quot;&gt;Mergify&lt;/a&gt; yourself, it&apos;s a single click log-in using your GitHub account. Check it out!&lt;/p&gt;
</content:encoded></item><item><title>Attending FOSDEM 2020</title><link>https://julien.danjou.info/blog/attending-fosdem-2020/</link><guid isPermaLink="true">https://julien.danjou.info/blog/attending-fosdem-2020/</guid><description>This weekend, I&apos;ve been lucky to attend again the FOSDEM conference, one of the largest open-source conference out there.</description><pubDate>Thu, 06 Feb 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This weekend, I&apos;ve been lucky to attend again the &lt;a href=&quot;https://fosdem.org/2020/&quot;&gt;FOSDEM&lt;/a&gt; conference, one of the largest open-source conference out there.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/02/Screenshot-2020-02-05-at-15.54.48.png&quot; alt=&quot;Screenshot of the FOSDEM 2020 Python devroom schedule&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I had a talk scheduled in the &lt;a href=&quot;https://fosdem.org/2020/schedule/track/python/&quot;&gt;Python devroom&lt;/a&gt; on Saturday about &lt;a href=&quot;https://fosdem.org/2020/schedule/event/python2020_profiling/&quot;&gt;building a production-ready profiling in Python&lt;/a&gt;. This was a good overview of the work I&apos;ve been doing at &lt;a href=&quot;https://datadoghq.com&quot;&gt;Datadog&lt;/a&gt; for the last few months.&lt;/p&gt;
&lt;p&gt;The video and slides are available below.&lt;/p&gt;
&lt;p&gt;Your browser does not support the video tag.&lt;/p&gt;
&lt;p&gt;The talk went well, attended by a few hundred people. I had a few interesting exchanges with people being interested and having some ideas about improvement.&lt;/p&gt;
</content:encoded></item><item><title>Python Logging with Datadog</title><link>https://julien.danjou.info/blog/python-logging-with-datadog/</link><guid isPermaLink="true">https://julien.danjou.info/blog/python-logging-with-datadog/</guid><description>At Mergify, we generate a pretty large amount of logs. Every time an event is received from GitHub for a particular pull request, our engine computes a new state for it.</description><pubDate>Mon, 03 Feb 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At &lt;a href=&quot;https://mergify.io&quot;&gt;Mergify&lt;/a&gt;, we generate a pretty large amount of logs. Every time an event is received from GitHub for a particular pull request, our engine computes a new state for it. Doing so, it logs some informational statements about what it&apos;s doing — and any error that might happen.&lt;/p&gt;
&lt;p&gt;This information is precious to us. Without proper logging, it&apos;d be utterly impossible for us to debug any issue. As we needed to store and index our logs somewhere, we picked Datadog as our log storage provider.&lt;/p&gt;
&lt;p&gt;Datadog offers real-time indexing of our logs. The ability to search our records that fast is compelling as we&apos;re able to retrieve log about a GitHub repository or a pull request with a single click.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/01/Screenshot-2020-01-06-at-17.23.58.png&quot; alt=&quot;Our custom Datadog log facets&quot; /&gt;&lt;/p&gt;
&lt;p&gt;To achieve this result, we had to inject our Python application logs into Datadog. To set up the Python logging mechanism, we rely on &lt;a href=&quot;https://github.com/jd/daiquiri&quot;&gt;&lt;em&gt;daiquiri&lt;/em&gt;&lt;/a&gt;, a fantastic library I maintained for several years now. &lt;em&gt;Daiquiri&lt;/em&gt; leverages the regular Python &lt;code&gt;logging&lt;/code&gt; module, making its a no-brainer to set up and offering a few extra features.&lt;/p&gt;
&lt;p&gt;We recently added native support for the Datadog agent in &lt;em&gt;daiquiri&lt;/em&gt;, making it even more straightforward to log from your Python application.&lt;/p&gt;
&lt;h2&gt;Enabling log on the Datadog agent&lt;/h2&gt;
&lt;p&gt;Datadog has &lt;a href=&quot;https://docs.datadoghq.com/agent/logs/?tab=tailexistingfiles&quot;&gt;extensive documentation on how to configure its agent&lt;/a&gt;. This can be summarized to adding &lt;code&gt;logs_enabled: true&lt;/code&gt; in your agent configuration. Simple as that.&lt;/p&gt;
&lt;p&gt;You then need to create a new source for the agent. The easiest way to connect your application and the Datadog agent is using the TCP socket. Your application will write logs directly to the Datadog agent, which will forward the entries to Datadog backend.&lt;/p&gt;
&lt;p&gt;Create a configuration file in &lt;code&gt;conf.d/python.d/conf.yaml&lt;/code&gt; with the following content:&lt;/p&gt;
&lt;h2&gt;Setting up &lt;code&gt;daiquiri&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Once this is done, you need to configure your Python application to log to the TCP socket configured in the agent above.&lt;/p&gt;
&lt;p&gt;The Datadog agent expects logs in JSON format being sent, which is what &lt;em&gt;daiquiri&lt;/em&gt; does for you. Using JSON allows to embed any extra fields to leverage fast search and indexing. As &lt;em&gt;daiquiri&lt;/em&gt; provides native handling for extra fields, you&apos;ll be able to send those extra fields without trouble.&lt;/p&gt;
&lt;p&gt;First, list &lt;em&gt;daiquiri&lt;/em&gt; in your application dependency. Then, set up logging in your application this way:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import daiquiri

daiquiri.setup(
  outputs=[
    daiquiri.output.Datadog(),
  ],
  level=logging.INFO,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This configuration logs to the default TCP destination &lt;code&gt;localhost:10518&lt;/code&gt; — though you can pass the &lt;code&gt;host&lt;/code&gt; and &lt;code&gt;port&lt;/code&gt; argument to change that. You can customize the outputs as you wish by checking out &lt;a href=&quot;https://daiquiri.readthedocs.io/en/latest/&quot;&gt;daiquiri documentation&lt;/a&gt;. For example, you could also include logging to &lt;code&gt;stdout&lt;/code&gt; by adding &lt;code&gt;daiquiri.output.Stream(sys.stdout)&lt;/code&gt; in the output list.&lt;/p&gt;
&lt;h2&gt;Using &lt;code&gt;extra&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;When using &lt;em&gt;daiquiri&lt;/em&gt;, you&apos;re free to use &lt;code&gt;logging.getLogger&lt;/code&gt; to get your regular logging object. However, by using the alternative &lt;code&gt;daiquiri.getLogger&lt;/code&gt; function, you&apos;re enabling the native use of extra arguments — which is quite handy. That means you can pass any arbitrary key/value to your log call, and see it up being embedded in your log data — up to Datadog.&lt;/p&gt;
&lt;p&gt;Here&apos;s an example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import daiquiri

[…]

log = daiquiri.getLogger(__name__)
log.info(&quot;User did something important&quot;, user=user, request_id=request_id)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The extra keyword argument passed to &lt;code&gt;log.info&lt;/code&gt; will be directly shown as attributes in Datadog logs:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/01/Screenshot-2020-01-06-at-18.22.04.png&quot; alt=&quot;One of the log line of our Mergify engine&quot; /&gt;&lt;/p&gt;
&lt;p&gt;All those attributes can then be used to search or to display custom views. This is really powerful to monitor and debug any kind of service.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/01/Screenshot-2020-01-06-at-18.39.05.png&quot; alt=&quot;Screenshot of Datadog log explorer showing custom attributes for search and display&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;A log object per object&lt;/h2&gt;
&lt;p&gt;When passing &lt;em&gt;extra&lt;/em&gt; arguments, it is easy to make mistakes and forget some. This especially can happen when your application wants to log information for a particular object.&lt;/p&gt;
&lt;p&gt;The best pattern to avoid this is to create a custom log object per object:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import daiquiri

class MyObject:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.log = daiquiri.getLogger(&quot;MyObject&quot;, x=self.x, y=self.y)

    def do_something(self):
        try:
            self.call_this()
        except Exception:
            self.log.error(&quot;Something bad happened&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By using the &lt;code&gt;self.log&lt;/code&gt; object as defined above, there&apos;s no way for your application to miss some extra fields for an object. All your logs will look in the same style and will end up being indexed correctly in Datadog.&lt;/p&gt;
&lt;h2&gt;Log Design&lt;/h2&gt;
&lt;p&gt;The &lt;em&gt;extra&lt;/em&gt; arguments from the Python loggers are often dismissed, and many developers stick to logging strings with various information included inside. Having a proper explanation string, plus a few extra key/value pairs that are parsable by machines and humans, is a better way to do logging. Leveraging engines such as Datadog allow to store and query those logs in a snap.&lt;/p&gt;
&lt;p&gt;This is way more efficient than trying to parse and grep strings yourselves!&lt;/p&gt;
</content:encoded></item><item><title>Atomic lock-free counters in Python</title><link>https://julien.danjou.info/blog/atomic-lock-free-counters-in-python/</link><guid isPermaLink="true">https://julien.danjou.info/blog/atomic-lock-free-counters-in-python/</guid><description>At Datadog, we&apos;re really into metrics. We love them, we store them, but we also generate them. To do that, you need to juggle with integers that are incremented, also known as counters.</description><pubDate>Mon, 06 Jan 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;At &lt;a href=&quot;https://datadog.com&quot;&gt;Datadog&lt;/a&gt;, we&apos;re really into metrics. We love them, we store them, but we also &lt;em&gt;generate&lt;/em&gt; them. To do that, you need to juggle with integers that are incremented, also known as &lt;em&gt;counters&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;While having an integer that changes its value sounds dull, it might not be without some surprises in certain circumstances. Let&apos;s dive in.&lt;/p&gt;
&lt;h2&gt;The Straightforward Implementation&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class SingleThreadCounter(object):
	def __init__(self):
    	self.value = 0
        
    def increment(self):
        self.value += 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pretty easy, right?&lt;/p&gt;
&lt;p&gt;Well, not so fast, buddy. As the class name implies, this works fine with a single-threaded application. Let&apos;s take a look at the instructions in the &lt;code&gt;increment&lt;/code&gt; method:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; import dis
&amp;gt;&amp;gt;&amp;gt; dis.dis(&quot;self.value += 1&quot;)
  1           0 LOAD_NAME                0 (self)
              2 DUP_TOP
              4 LOAD_ATTR                1 (value)
              6 LOAD_CONST               0 (1)
              8 INPLACE_ADD
             10 ROT_TWO
             12 STORE_ATTR               1 (value)
             14 LOAD_CONST               1 (None)
             16 RETURN_VALUE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;self.value +=1&lt;/code&gt; line of code generates 8 different operations for Python. Operations that could be interrupted at any time in their flow to switch to a different thread that could also increment the counter.&lt;/p&gt;
&lt;p&gt;Indeed, the &lt;code&gt;+=&lt;/code&gt; operation is not atomic: one needs to do a &lt;code&gt;LOAD_ATTR&lt;/code&gt; to read the current value of the counter, then an &lt;code&gt;INPLACE_ADD&lt;/code&gt; to add 1, to finally &lt;code&gt;STORE_ATTR&lt;/code&gt; to store the final result in the &lt;code&gt;value&lt;/code&gt; attribute.&lt;/p&gt;
&lt;p&gt;If another thread executes the same code at the same time, you could end up with adding 1 to an old value:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Thread-1 reads the value as 23
Thread-1 adds 1 to 23 and get 24
Thread-2 reads the value as 23
Thread-1 stores 24 in value
Thread-2 adds 1 to 23
Thread-2 stores 24 in value
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Boom. Your &lt;code&gt;Counter&lt;/code&gt; class is not thread-safe. 😭&lt;/p&gt;
&lt;h2&gt;The Thread-Safe Implementation&lt;/h2&gt;
&lt;p&gt;To make this thread-safe, a &lt;em&gt;lock&lt;/em&gt; is necessary. We need a lock each time we want to increment the value, so we are sure the increments are done serially.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import threading

class FastReadCounter(object):
    def __init__(self):
        self.value = 0
        self._lock = threading.Lock()
        
    def increment(self):
        with self._lock:
            self.value += 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This implementation is thread-safe. There is no way for multiple threads to increment the value at the same time, so there&apos;s no way that an increment is lost.&lt;/p&gt;
&lt;p&gt;The only downside of this counter implementation is that you need to lock the counter each time you need to increment. There might be much contention around this lock if you have many threads updating the counter.&lt;/p&gt;
&lt;p&gt;On the other hand, if it&apos;s barely updated and often read, this is an excellent implementation of a thread-safe counter.&lt;/p&gt;
&lt;h2&gt;A Fast Write Implementation&lt;/h2&gt;
&lt;p&gt;There&apos;s a way to implement a thread-safe counter in Python that does not need to be locked on write. It&apos;s a trick that should only work on CPython because of the &lt;em&gt;Global Interpreter Lock&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;While everybody is unhappy with it, this time, the GIL is going to help us. When a C function is executed and does not do any I/O, it cannot be interrupted by any other thread. It turns out there&apos;s a counter-like class implemented in Python: &lt;a href=&quot;https://docs.python.org/3/library/itertools.html#itertools.count&quot;&gt;itertools.count&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We can use this &lt;code&gt;count&lt;/code&gt; class as our advantage by avoiding the need to use a lock when incrementing the counter.&lt;/p&gt;
&lt;p&gt;If you read the documentation for &lt;code&gt;itertools.count&lt;/code&gt;, you&apos;ll notice that there&apos;s no way to read the current value of the counter. This is tricky, and this is where we&apos;ll need to use a lock to bypass this limitation. Here&apos;s the code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import itertools
import threading

class FastWriteCounter(object):
    def __init__(self):
        self._number_of_read = 0
        self._counter = itertools.count()
        self._read_lock = threading.Lock()

    def increment(self):
        next(self._counter)

    def value(self):
        with self._read_lock:
            value = next(self._counter) - self._number_of_read
            self._number_of_read += 1
        return value
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;increment&lt;/code&gt; code is quite simple in this case: the counter is just incremented without any lock. The GIL protects concurrent access to the internal data structure in C, so there&apos;s no need for us to lock anything.&lt;/p&gt;
&lt;p&gt;On the other hand, Python does not provide any way to read the value of an &lt;code&gt;itertools.count&lt;/code&gt; object. We need to use a small trick to get the current value. The &lt;code&gt;value&lt;/code&gt; method increments the counter and then gets the value while subtracting the number of times the counter has been read (and therefore incremented for nothing).&lt;/p&gt;
&lt;p&gt;This counter is, therefore, lock-free for writing, but not for reading. The opposite of our previous implementation&lt;/p&gt;
&lt;h2&gt;Measuring Performance&lt;/h2&gt;
&lt;p&gt;After writing all of this code, I wanted to make sure how the different implementations impacted speed. Using the &lt;a href=&quot;https://docs.python.org/3/library/timeit.html&quot;&gt;timeit&lt;/a&gt; module and my fancy laptop, I&apos;ve measured the performance of reading and writing to this counter.&lt;/p&gt;
&lt;p&gt;Operation&lt;/p&gt;
&lt;p&gt;SingleThreadCounter&lt;/p&gt;
&lt;p&gt;FastReadCounter&lt;/p&gt;
&lt;p&gt;FastWriteCounter&lt;/p&gt;
&lt;p&gt;&lt;code&gt;increment&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;176 ns&lt;/p&gt;
&lt;p&gt;390 ns&lt;/p&gt;
&lt;p&gt;169 ns&lt;/p&gt;
&lt;p&gt;&lt;code&gt;value&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;26 ns&lt;/p&gt;
&lt;p&gt;26 ns&lt;/p&gt;
&lt;p&gt;529 ns&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Benchmark table comparing counter performance for read and increment operations&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I&apos;m glad that the performance measurements in practice match the theory 😅. Both &lt;code&gt;SingleThreadCounter&lt;/code&gt; and &lt;code&gt;FastReadCounter&lt;/code&gt; have the same performance for reading. Since they use a simple variable read, it makes absolute sense.&lt;/p&gt;
&lt;p&gt;The same goes for &lt;code&gt;SingleThreadCounter&lt;/code&gt; and &lt;code&gt;FastWriteCounter&lt;/code&gt;, which have the same performance for incrementing the counter. Again they&apos;re using the same kind of lock-free code to add 1 to an integer, making the code fast.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;It&apos;s pretty obvious, but if you&apos;re using a single-threaded application and do not have to care about concurrent access, you should stick to using a simple incremented integer.&lt;/p&gt;
&lt;p&gt;For fun, I&apos;ve published a Python package named &lt;a href=&quot;https://pypi.org/project/fastcounter/&quot;&gt;fastcounter&lt;/a&gt; that provides those classes. The &lt;a href=&quot;https://github.com/jd/fastcounter&quot;&gt;sources are available on GitHub&lt;/a&gt;. Enjoy!&lt;/p&gt;
</content:encoded></item><item><title>Properly managing your .gitignore file</title><link>https://julien.danjou.info/blog/properly-managing-your-gitignore/</link><guid isPermaLink="true">https://julien.danjou.info/blog/properly-managing-your-gitignore/</guid><description>There&apos;s not a single month where I don&apos;t have to explain this. I thought it&apos;d be a good opportunity to write about this .gitignore file so everyone is up to date on this magic file.  The purpose of .g</description><pubDate>Mon, 02 Dec 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;There&apos;s not a single month where I don&apos;t have to explain this. I thought it&apos;d be a good opportunity to write about this &lt;code&gt;.gitignore&lt;/code&gt; file so everyone is up to date on this magic file.&lt;/p&gt;
&lt;h2&gt;The purpose of &lt;code&gt;.gitignore&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;.gitignore&lt;/code&gt; file is meant to be a list of files that &lt;em&gt;Git&lt;/em&gt; should not track. It resides at the root directory of your repository. It can be a list of file path relative to the repository, or a list of wildcard. &lt;a href=&quot;https://git-scm.com/docs/gitignore&quot;&gt;The file format and location is fully documented in Git documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For example, this is a valid content for a &lt;code&gt;.gitignore&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;foo
bar/*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you&apos;re using Git commands such as &lt;code&gt;git add&lt;/code&gt;, all the files matching what&apos;s listed in &lt;code&gt;.gitignore&lt;/code&gt; are ignored. That makes sure you don&apos;t commit a file that should not be there by mistake. In the example above, any file in the &lt;code&gt;bar&lt;/code&gt; directory or any file named &lt;code&gt;foo&lt;/code&gt; will be completely ignored by all &lt;em&gt;Git&lt;/em&gt; commands.&lt;/p&gt;
&lt;p&gt;Awesome!&lt;/p&gt;
&lt;h3&gt;What&apos;s the problem with it?&lt;/h3&gt;
&lt;p&gt;Soon, developers realize that their directory is cluttered with temporary files. It might be from their build system, their editors or some test files they wrote.&lt;/p&gt;
&lt;p&gt;So what do they do? They add those files to &lt;code&gt;.gitignore&lt;/code&gt; for their project. You end up with a &lt;code&gt;.gitignore&lt;/code&gt; file that contains entries like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;*~
.vscode
*.DS_Store
.idea
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that, you&apos;re sure to ignore backup files from &lt;em&gt;vim&lt;/em&gt;, folders from MacOS and temporary files from Visual Studio code, etc.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/11/giphy.gif&quot; alt=&quot;Animated reaction GIF expressing frustration with cluttered gitignore files&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Don&apos;t do this. Not everybody uses your editor or favorite pet tool, and nobody cares. The repository you&apos;re working in is shared with a lot of others developers. Sending pull requests to just add this kind of entry to ignore files generated by your pet editor is &lt;em&gt;wrong and annoying&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;Wait, how do I ignore my editor files then?&lt;/h3&gt;
&lt;p&gt;If you read through &lt;em&gt;Git&lt;/em&gt; documentation, the answer lies there: &lt;em&gt;Git&lt;/em&gt; has a global ignore file that works for &lt;strong&gt;EVERY&lt;/strong&gt; repository on your system. No need to hack &lt;em&gt;each&lt;/em&gt; repository. By default, it&apos;s in &lt;code&gt;~/.config/git/ignore&lt;/code&gt;. Here&apos;s mine:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.#*
*.swp
.DS_Store
.dir-locals.el
.dir-locals-2.el
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s enough to ignore my editors and OS files in all my repositories so I don&apos;t &lt;code&gt;git add&lt;/code&gt; wrong files by mistake. You can tweak this global file location by changing by tweaking &lt;code&gt;core.excludesFile&lt;/code&gt; in your &lt;em&gt;Git&lt;/em&gt; configuration.&lt;/p&gt;
&lt;h3&gt;So what should I put in .gitignore?&lt;/h3&gt;
&lt;p&gt;You should put in &lt;code&gt;.gitignore&lt;/code&gt; all files and patterns that are generated by the build system of your project, or any file that it might output while running.&lt;/p&gt;
&lt;p&gt;For example, for a Python project, it&apos;s common to have this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;*.pyc
__pycache__
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this, it makes sure that nobody is committing compiled Python files.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/11/tumblr_ma0id4kYlv1rv5x9bo1_400.gif&quot; alt=&quot;Animated GIF celebrating proper gitignore usage&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Thanks for reading through this. I hope you&apos;ll write better &lt;code&gt;.gitignore&lt;/code&gt; files in the future. 🤞&lt;/p&gt;
</content:encoded></item><item><title>Finding definitions from a source file and a line number in Python</title><link>https://julien.danjou.info/blog/finding-definitions-from-a-source-file-and-a-line-number-in-python/</link><guid isPermaLink="true">https://julien.danjou.info/blog/finding-definitions-from-a-source-file-and-a-line-number-in-python/</guid><description>My job at Datadog keeps me busy with new and questioning challenges. I recently stumbled upon a problem that sounded easy but was more difficult than I imagined.</description><pubDate>Mon, 04 Nov 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;My job at &lt;a href=&quot;https://datadog.com&quot;&gt;Datadog&lt;/a&gt; keeps me busy with new and questioning challenges. I recently stumbled upon a problem that sounded easy but was more difficult than I imagined.&lt;/p&gt;
&lt;p&gt;Here&apos;s the thing: considering a filename and a line number, can you tell which function, method or class this line of code belongs to?&lt;/p&gt;
&lt;p&gt;I started to dig into the standard library, but I did not find anything solving this problem. It sounded like I had to write this myself.&lt;/p&gt;
&lt;p&gt;The first steps sound easy. Open a file, read it, find the line number. Right.&lt;/p&gt;
&lt;p&gt;Then, how do you know which functions this line is in? You don&apos;t, expect if you parse the whole file and keep tracks of function definitions. A regular expression parsing each line might be a solution?&lt;/p&gt;
&lt;p&gt;Well, you had to be careful as function definitions can span multiple lines.&lt;/p&gt;
&lt;h2&gt;Using the AST&lt;/h2&gt;
&lt;p&gt;I decided that a good and robust strategy was not going to use manual parsing or the like, but using Python abstract syntax tree (AST) directly. By leveraging Python&apos;s own parsing code, I was sure I was not going to fail while parsing a Python source file.&lt;/p&gt;
&lt;p&gt;This can be simply be accomplished with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import ast

def parse_file(filename):
    with open(filename) as f:
        return ast.parse(f.read(), filename=filename)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And you&apos;re done. Are you? No, because that only works in 99.99% of the case. If your source file is using an encoding that is now ASCII or UTF-8, then the function fails. I know you think I&apos;m crazy to think about this but I like my code to be robust.&lt;/p&gt;
&lt;p&gt;It turns out Python has a cookie to specify the encoding in the form of &lt;code&gt;# encoding: utf-8&lt;/code&gt; as defined in &lt;a href=&quot;https://www.python.org/dev/peps/pep-0263/&quot;&gt;PEP 263&lt;/a&gt;. Reading this cookie would help to find the encoding.&lt;/p&gt;
&lt;p&gt;To do that, we need to open the file in binary mode, use a regular expression to match the data, and… Well, it&apos;s dull, and somebody already implemented it for us so let&apos;s use the fantastic &lt;code&gt;[tokenize.open](https://docs.python.org/3/library/tokenize.html#tokenize.open)&lt;/code&gt; function provided by Python:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import ast
import tokenize

def parse_file(filename):
    with tokenize.open(filename) as f:
        return ast.parse(f.read(), filename=filename)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That should work in 100% of the time. Until proven otherwise.&lt;/p&gt;
&lt;h2&gt;Browsing the AST&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;parse_file&lt;/code&gt; function now returns a Python AST. If you never played with Python AST, it&apos;s a gigantic tree that represents your source code just before it is compiled down to Python bytecode.&lt;/p&gt;
&lt;p&gt;In the tree, there should be statements and expression. In our case, we&apos;re interested in finding the function definition that is the closest to our line number. Here&apos;s an implementation of that function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def filename_and_lineno_to_def(filename, lineno):
    candidate = None
    for item in ast.walk(parse_file(filename)):
        if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
            if item.lineno &amp;gt; lineno:
                # Ignore whatever is after our line
                continue
            if candidate:
                distance = lineno - item.lineno
                if distance &amp;lt; (lineno - candidate.lineno):
                    candidate = item
            else:
                candidate = item

    if candidate:
        return candidate.name
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This iterates over all the node of the AST and returns the node where the line number is the closest to our definition. If we have a file that contains:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class A(object):
    X = 1
    def y(self):
        return 42
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;the function &lt;code&gt;filename_and_lineno_to_def&lt;/code&gt; returns for the lines 1 to 5:&lt;/p&gt;
&lt;p&gt;It works!&lt;/p&gt;
&lt;h2&gt;Closures?&lt;/h2&gt;
&lt;p&gt;The naive approach described earlier likely works for 90% of your code, but there are some edge cases. For example, when defining function closures, the above algorithm fails. With the following code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class A(object):
   X = 1
   def y(self):
       def foo():
           return 42
       return foo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;the function &lt;code&gt;filename_and_lineno_to_def&lt;/code&gt; returns for lines 1 to 7:&lt;/p&gt;
&lt;p&gt;Oops. Clearly, lines 6 and 7 do not belong to the &lt;code&gt;foo&lt;/code&gt; function. Our approach is too naive to see that starting at line 6, we&apos;re back in the &lt;code&gt;y&lt;/code&gt; method.&lt;/p&gt;
&lt;h2&gt;Interval Trees&lt;/h2&gt;
&lt;p&gt;The correct way of handling that is to consider each function definition as an interval:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/06/interval-tree.png&quot; alt=&quot;Piece of code seen as interval.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Whatever the line number we request is, we should return the node that is responsible for the smallest interval that the line is in.&lt;/p&gt;
&lt;p&gt;What we need in this case is a correct data structure to solve our problem: an &lt;a href=&quot;https://en.wikipedia.org/wiki/Interval_tree&quot;&gt;interval tree&lt;/a&gt; fits perfectly our use case. It allows for searching rapidly pieces of code that match our line number.&lt;/p&gt;
&lt;p&gt;To solve our problem we need several things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A way to compute the beginning and end line numbers for a function.&lt;/li&gt;
&lt;li&gt;A tree that is fed with the intervals we computed just before.&lt;/li&gt;
&lt;li&gt;A way to select the best matching intervals if a line is part of several functions (closure).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Computing Function Intervals&lt;/h2&gt;
&lt;p&gt;The interval of a function is the first and last lines that compose its body. It&apos;s pretty easy to find those by walking through the function AST node:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def _compute_interval(node):
    min_lineno = node.lineno
    max_lineno = node.lineno
    for node in ast.walk(node):
        if hasattr(node, &quot;lineno&quot;):
            min_lineno = min(min_lineno, node.lineno)
            max_lineno = max(max_lineno, node.lineno)
    return (min_lineno, max_lineno + 1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Given any AST node, the function returns a tuple of the first and last line number of that node.&lt;/p&gt;
&lt;h2&gt;Building The Tree&lt;/h2&gt;
&lt;p&gt;Rather than implementing an interval tree, we&apos;ll use the &lt;a href=&quot;https://pypi.org/project/intervaltree/&quot;&gt;intervaltree&lt;/a&gt; library. We need to create a tree and feed it with the computed interval:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def file_to_tree(filename):
    with tokenize.open(filename) as f:
        parsed = ast.parse(f.read(), filename=filename)
    tree = intervaltree.IntervalTree()
    for node in ast.walk(parsed):
        if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
            start, end = _compute_interval(node)
            tree[start:end] = node
    return tree
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here you go: the function parses the Python file passed as an argument and converts it to its AST representation. It then walks it and feeds the interval tree with every class and function definition.&lt;/p&gt;
&lt;h2&gt;Querying the Tree&lt;/h2&gt;
&lt;p&gt;Now that the tree is built, it should be queried with the line number. This is pretty simple:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;matches = file_to_tree(filename)[lineno]
if matches:
    return min(matches, key=lambda i: i.length()).data.name
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The build tree might return several matches if there are several intervals containing our line number. In that case, we pick the smallest interval and return the name of the node — which is our class or function name!&lt;/p&gt;
&lt;h2&gt;Mission Success&lt;/h2&gt;
&lt;p&gt;We did it! We started with a naive approach and iterated to a final solution covering 100% of our cases. Picking the right data structure, interval trees here, helped us solving this in an intelligent approach.&lt;/p&gt;
</content:encoded></item><item><title>Sending Emails in Python — Tutorial with Code Examples</title><link>https://julien.danjou.info/blog/sending-emails-in-python-tutorial-code-examples/</link><guid isPermaLink="true">https://julien.danjou.info/blog/sending-emails-in-python-tutorial-code-examples/</guid><description>What do you need to send an email with Python? Some basic programming and web knowledge along with the elementary Python skills. I assume you’ve already had a web app built with this language and now</description><pubDate>Tue, 15 Oct 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;What do you need to send an email with Python? Some basic programming and web knowledge along with the elementary Python skills. I assume you’ve already had a web app built with this language and now you need to extend its functionality with notifications or other emails sending. This tutorial will guide you through the most essential steps of sending emails via an SMTP server:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Configuring a server for testing (do you know why it’s important?)&lt;/li&gt;
&lt;li&gt;Local SMTP server&lt;/li&gt;
&lt;li&gt;Mailtrap test SMTP server&lt;/li&gt;
&lt;li&gt;Different types of emails: HTML, with images, and attachments&lt;/li&gt;
&lt;li&gt;Sending multiple personalized emails (Python is just invaluable for email automation)&lt;/li&gt;
&lt;li&gt;Some popular email sending options like Gmail and transactional email services&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;Served with numerous code examples written and tested on Python 3.7!&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Sending an email using an SMTP&lt;/h3&gt;
&lt;p&gt;The first good news about Python is that it has a built-in module for sending emails via SMTP in its standard library. No extra installations or tricks are required. You can import the module using the following statement:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import smtplib
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To make sure that the module has been imported properly and get the full description of its classes and arguments, type in an interactive Python session:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;help(smtplib)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At our next step, we will talk a bit about servers: choosing the right option and configuring it.&lt;/p&gt;
&lt;h4&gt;An SMTP server for testing emails in Python&lt;/h4&gt;
&lt;p&gt;When creating a new app or adding any functionality, especially when doing it for the first time, it’s essential to experiment on a test server. Here is a brief list of reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You won’t hit your friends’ and customers’ inboxes. This is vital when you test bulk email sending or work with an email database.&lt;/li&gt;
&lt;li&gt;You won’t flood your own inbox with testing emails.&lt;/li&gt;
&lt;li&gt;Your domain won’t be blacklisted for spam.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Local SMTP server&lt;/h4&gt;
&lt;p&gt;If you prefer working in the local environment, the local SMTP debugging server might be an option. For this purpose, Python offers an &lt;em&gt;smtpd&lt;/em&gt; module. It has a &lt;code&gt;DebuggingServer&lt;/code&gt; feature, which will discard messages you are sending out and will print them to &lt;code&gt;stdout&lt;/code&gt;. It is compatible with all operations systems.&lt;/p&gt;
&lt;p&gt;Set your SMTP server to &lt;em&gt;localhost:1025&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python -m smtpd -n -c DebuggingServer localhost:1025
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In order to run SMTP server on port 25, you’ll need root permissions:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo python -m smtpd -n -c DebuggingServer localhost:25
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It will help you verify whether your code is working and point out the possible problems if there are any. However, it won’t give you the opportunity to check how your HTML email template is rendered.&lt;/p&gt;
&lt;h4&gt;Fake SMTP server&lt;/h4&gt;
&lt;p&gt;Fake SMTP server imitates the work of a real 3rd party web server. In further examples in this post, we will use &lt;a href=&quot;https://mailtrap.io&quot;&gt;Mailtrap&lt;/a&gt;. Beyond testing email sending, it will let us check how the email will  be rendered and displayed, review the message raw data as well as will provide us with a spam report. Mailtrap is very easy to set up: you will need just copy the credentials generated by the app and paste them into your code.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://lh3.googleusercontent.com/xBVM7uyt4Q6mtpLLTiCBze9lNV-dpkO2rMBLSazZ9gb8LImFDgzZWVIOTCtke87LBixqrsJF-pii7usO3ezPbgjOWGRj7isa_ap2-EXK5GiHmSz4mtwenUIi-f_s05CfxQJoHGvl&quot; alt=&quot;Screenshot of Mailtrap fake SMTP server setup interface&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Here is how it looks in practice:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import smtplib

port = 2525
smtp_server = &quot;smtp.mailtrap.io&quot;
login = &quot;1a2b3c4d5e6f7g&quot; # your login generated by Mailtrap
password = &quot;1a2b3c4d5e6f7g&quot; # your password generated by Mailtrap
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Mailtrap makes things even easier. Go to the &lt;em&gt;Integrations&lt;/em&gt; section in the SMTP settings tab and get the ready-to-use template of the simple message, with your Mailtrap credentials in it. It is the most basic option of instructing your Python script on who sends what to who is the &lt;em&gt;sendmail()&lt;/em&gt; instance method:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://lh5.googleusercontent.com/eKUJ__R4SYnY5jdvPiPPucHnoaOMBUxHZIu0DT2NjnTMU2FhvObBzqVN-qgCOTeSIm7yc_ifUAe5a0RofkbNdxOqNrzAw1icea4c9WIyb6NGk8KMmIvctLgUPlblmzFMSeaRnbGQ&quot; alt=&quot;Screenshot of Mailtrap integration settings with Python SMTP credentials&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The code looks pretty straightforward, right? Let’s take a closer look at it and add some error handling (see the comments in between). To catch errors, we use the &lt;code&gt;try&lt;/code&gt; and &lt;code&gt;except&lt;/code&gt; blocks.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## The first step is always the same: import all necessary components:
import smtplib
from socket import gaierror

## Now you can play with your code. Let’s define the SMTP server separately here:
port = 2525
smtp_server = &quot;smtp.mailtrap.io&quot;
login = &quot;1a2b3c4d5e6f7g&quot; # paste your login generated by Mailtrap
password = &quot;1a2b3c4d5e6f7g&quot; # paste your password generated by Mailtrap

## Specify the sender’s and receiver’s email addresses:
sender = &quot;from@example.com&quot;
receiver = &quot;mailtrap@example.com&quot;

## Type your message: use two newlines (\n) to separate the subject from the message body, and use &apos;f&apos; to  automatically insert variables in the text
message = f&quot;&quot;&quot;\
Subject: Hi Mailtrap
To: {receiver}
From: {sender}
This is my first message with Python.&quot;&quot;&quot;

try:
  # Send your message with credentials specified above
  with smtplib.SMTP(smtp_server, port) as server:
    server.login(login, password)
    server.sendmail(sender, receiver, message)
except (gaierror, ConnectionRefusedError):
  # tell the script to report if your message was sent or which errors need to be fixed
  print(&apos;Failed to connect to the server. Bad connection settings?&apos;)
except smtplib.SMTPServerDisconnected:
  print(&apos;Failed to connect to the server. Wrong user/password?&apos;)
except smtplib.SMTPException as e:
  print(&apos;SMTP error occurred: &apos; + str(e))
else:
  print(&apos;Sent&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you get the &lt;em&gt;Sent&lt;/em&gt; result in Shell, you should see your message in your Mailtrap inbox:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://lh3.googleusercontent.com/xCCQOuWFyqmvbiOaLa7VgYyBdCu5c2q5oXzyn2aeFFE8tkfbUvDwi_H19fSNAempeUWIoDuHVn5ETqr34lO8WkT8vZh8iJVChjnCZgoAA3TsTJF2n32sGUl1GX89WcYUdChJZ2Ux&quot; alt=&quot;Screenshot of a test email received in the Mailtrap inbox&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Sending emails with HTML content&lt;/h3&gt;
&lt;p&gt;In most cases, you need to add some formatting, links, or images to your email notifications. We can simply put all of these with the HTML content. For this purpose, Python has an &lt;em&gt;email&lt;/em&gt; package.&lt;/p&gt;
&lt;p&gt;We will deal with the MIME message type, which is able to combine HTML and plain text. In Python, it is handled by the &lt;em&gt;email.mime&lt;/em&gt; module.&lt;/p&gt;
&lt;p&gt;It is better to write a text version and an HTML version separately, and then merge them with the &lt;code&gt;MIMEMultipart(&quot;alternative&quot;)&lt;/code&gt; instance. It means that such a message has two rendering options accordingly. In case an HTML isn’t be rendered successfully for some reason, a text version will still be available.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

port = 2525
smtp_server = &quot;smtp.mailtrap.io&quot;
login = &quot;1a2b3c4d5e6f7g&quot; # paste your login generated by Mailtrap
password = &quot;1a2b3c4d5e6f7g&quot; # paste your password generated by Mailtrap

sender_email = &quot;mailtrap@example.com&quot;
receiver_email = &quot;new@example.com&quot;

message = MIMEMultipart(&quot;alternative&quot;)
message[&quot;Subject&quot;] = &quot;multipart test&quot;
message[&quot;From&quot;] = sender_email
message[&quot;To&quot;] = receiver_email
## Write the plain text part
text = &quot;&quot;&quot;\ Hi, Check out the new post on the Mailtrap blog: SMTP Server for Testing: Cloud-based or Local? https://blog.mailtrap.io/2018/09/27/cloud-or-local-smtp-server/ Feel free to let us know what content would be useful for you!&quot;&quot;&quot;

## write the HTML part
html = &quot;&quot;&quot;\ &amp;lt;html&amp;gt; &amp;lt;body&amp;gt; &amp;lt;p&amp;gt;Hi,&amp;lt;br&amp;gt; Check out the new post on the Mailtrap blog:&amp;lt;/p&amp;gt; &amp;lt;p&amp;gt;&amp;lt;a href=&quot;https://blog.mailtrap.io/2018/09/27/cloud-or-local-smtp-server&quot;&amp;gt;SMTP Server for Testing: Cloud-based or Local?&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt; &amp;lt;p&amp;gt; Feel free to &amp;lt;strong&amp;gt;let us&amp;lt;/strong&amp;gt; know what content would be useful for you!&amp;lt;/p&amp;gt; &amp;lt;/body&amp;gt; &amp;lt;/html&amp;gt; &quot;&quot;&quot;

## convert both parts to MIMEText objects and add them to the MIMEMultipart message
part1 = MIMEText(text, &quot;plain&quot;)
part2 = MIMEText(html, &quot;html&quot;)
message.attach(part1)
message.attach(part2)

## send your email
with smtplib.SMTP(&quot;smtp.mailtrap.io&quot;, 2525) as server:
  server.login(login, password)
  server.sendmail( sender_email, receiver_email, message.as_string() )

print(&apos;Sent&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://lh6.googleusercontent.com/jRh9dfieiWa1JAH1o5eb62Pv4wPUgIPGWpyz5RJkcFaflS-JnWJ7nQfdkr5hp87iOoDT-dx9WyvPwngJsvQnMoe9iKqa7jg6hDklFOxaLeftGqNp8MgtE8YDS13UmLLkBeee5cPT&quot; alt=&quot;The resulting output&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Sending Emails with Attachments in Python&lt;/h3&gt;
&lt;p&gt;The next step in mastering sending emails with Python is attaching files. Attachments are still the MIME objects but we need to encode them with the &lt;em&gt;base64&lt;/em&gt; module. A couple of important points about the attachments:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Python lets you attach text files, images, audio files, and even applications. You just need to use the appropriate email class like &lt;code&gt;email.mime.audio.MIMEAudio&lt;/code&gt; or &lt;code&gt;email.mime.image.MIMEImage&lt;/code&gt;&lt;em&gt;.&lt;/em&gt; For the full information, refer to &lt;a href=&quot;https://docs.python.org/3/library/email.mime.html&quot;&gt;this section&lt;/a&gt; of the Python documentation.&lt;/li&gt;
&lt;li&gt;Remember about the file size: sending files over 20MB is a bad practice.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In transactional emails, the PDF files are the most frequently used: we usually get receipts, tickets, boarding passes, order confirmations, etc. So let’s review how to send a boarding pass as a PDF file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import smtplib
from email import encoders
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

port = 2525
smtp_server = &quot;smtp.mailtrap.io&quot;
login = &quot;1a2b3c4d5e6f7g&quot; # paste your login generated by Mailtrap
password = &quot;1a2b3c4d5e6f7g&quot; # paste your password generated by Mailtrap

subject = &quot;An example of boarding pass&quot;
sender_email = &quot;mailtrap@example.com&quot;
receiver_email = &quot;new@example.com&quot;

message = MIMEMultipart()
message[&quot;From&quot;] = sender_email
message[&quot;To&quot;] = receiver_email
message[&quot;Subject&quot;] = subject

## Add body to email
body = &quot;This is an example of how you can send a boarding pass in attachment with Python&quot;
message.attach(MIMEText(body, &quot;plain&quot;))

filename = &quot;yourBP.pdf&quot;
## Open PDF file in binary mode
## We assume that the file is in the directory where you run your Python script from
with open(filename, &quot;rb&quot;) as attachment:
## The content type &quot;application/octet-stream&quot; means that a MIME attachment is a binary file
part = MIMEBase(&quot;application&quot;, &quot;octet-stream&quot;)
part.set_payload(attachment.read())
## Encode to base64
encoders.encode_base64(part)
## Add header
part.add_header(&quot;Content-Disposition&quot;, f&quot;attachment; filename= {filename}&quot;)
## Add attachment to your message and convert it to string
message.attach(part)

text = message.as_string()
## send your email
with smtplib.SMTP(&quot;smtp.mailtrap.io&quot;, 2525) as server:
  server.login(login, password)
  server.sendmail(sender_email, receiver_email, text)

print(&apos;Sent&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://lh5.googleusercontent.com/xxqg_Ro8uggpJxjCKMmukQ2jJmwDeXasadM5HA0LeOUktOPYc-0iXp2xQZHkILyfdFWroJEz-UqgTr_zBEKISuydHmoqCAPrvikrC23VgCDawHBVH-9-ufmmfF556nsU-1vPJ2Ng&quot; alt=&quot;The received email with your PDF&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To attach several files&lt;/strong&gt;, you can call the &lt;code&gt;message.attach()&lt;/code&gt; method several times.&lt;/p&gt;
&lt;h4&gt;How to send an email with image attachment&lt;/h4&gt;
&lt;p&gt;Images, even if they are a part of the message body, are attachments as well. There are three types of them: CID attachments (embedded as a MIME object), &lt;em&gt;base64&lt;/em&gt; images (inline embedding), and linked images.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For adding a CID attachment,&lt;/strong&gt; we will create a MIME multipart message with &lt;code&gt;MIMEImage&lt;/code&gt; component:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import smtplib
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart

port = 2525
smtp_server = &quot;smtp.mailtrap.io&quot;
login = &quot;1a2b3c4d5e6f7g&quot; # paste your login generated by Mailtrap
password = &quot;1a2b3c4d5e6f7g&quot; # paste your password generated by Mailtrap

sender_email = &quot;mailtrap@example.com&quot;
receiver_email = &quot;new@example.com&quot;

message = MIMEMultipart(&quot;alternative&quot;)
message[&quot;Subject&quot;] = &quot;CID image test&quot;
message[&quot;From&quot;] = sender_email
message[&quot;To&quot;] = receiver_email

## write the HTML part
html = &quot;&quot;&quot;\
&amp;lt;html&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;img src=&quot;cid:myimage&quot;&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&quot;&quot;&quot;
part = MIMEText(html, &quot;html&quot;)
message.attach(part)

## We assume that the image file is in the same directory that you run your Python script from
with open(&apos;mailtrap.jpg&apos;, &apos;rb&apos;) as img:
  image = MIMEImage(img.read())
## Specify the  ID according to the img src in the HTML part
image.add_header(&apos;Content-ID&apos;, &apos;&amp;lt;myimage&amp;gt;&apos;)
message.attach(image)

## send your email
with smtplib.SMTP(&quot;smtp.mailtrap.io&quot;, 2525) as server:
  server.login(login, password)
  server.sendmail(sender_email, receiver_email, message.as_string())

print(&apos;Sent&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://lh4.googleusercontent.com/VzdSmA1lJli_ZX_m6KmJW7VW-am20z5Vr_RUxJP5ZHxC72fRImhDuZxEXV0o2mDr09JTEMzPykskHKWh1DuMLF_yoKl5eIsMiKpebmILpvYioDGfzU70hFjfxFIu-fPVZqWF7vc8&quot; alt=&quot;The received email with CID image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The CID image is shown both as a part of the HTML message and as an attachment. Messages with this image type are often considered spam: check the &lt;em&gt;Analytics&lt;/em&gt; tab in Mailtrap to see the spam rate and recommendations on its improvement. Many email clients — Gmail in particular — don’t display CID images in most cases. So let’s review &lt;strong&gt;how to embed a base64 encoded image instead.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Here we will use &lt;em&gt;base64&lt;/em&gt; module and experiment with the same image file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import base64

port = 2525
smtp_server = &quot;smtp.mailtrap.io&quot;
login = &quot;1a2b3c4d5e6f7g&quot; # paste your login generated by Mailtrap
password = &quot;1a2b3c4d5e6f7g&quot; # paste your password generated by Mailtrap
sender_email = &quot;mailtrap@example.com&quot;
receiver_email = &quot;new@example.com&quot;

message = MIMEMultipart(&quot;alternative&quot;)
message[&quot;Subject&quot;] = &quot;inline embedding&quot;
message[&quot;From&quot;] = sender_email
message[&quot;To&quot;] = receiver_email

## We assume that the image file is in the same directory that you run your Python script from
with open(&quot;image.jpg&quot;, &quot;rb&quot;) as image:
  encoded = base64.b64encode(image.read()).decode()

html = f&quot;&quot;&quot;\
&amp;lt;html&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;img src=&quot;data:image/jpg;base64,{encoded}&quot;&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&quot;&quot;&quot;
part = MIMEText(html, &quot;html&quot;)
message.attach(part)

## send your email
with smtplib.SMTP(&quot;smtp.mailtrap.io&quot;, 2525) as server:
  server.login(login, password)
  server.sendmail(sender_email, receiver_email, message.as_string())

print(&apos;Sent&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://lh5.googleusercontent.com/zMSMgzypDp3lL1o1M21RB1nr6Dcc5Tekq8ucJktZqzWHynM8-YR2I4Ze6Rp7TkHtDxmcfYMyZXe1F_5sQihWL7kwpEFmQhnCRrDhe9aPjlJ0E7FzmdNvvibUOIU2yGqqC3U3ULEl&quot; alt=&quot;A base64 encoded image&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now the image is embedded into the HTML message and is not available as an attached file. Python has encoded our JPEG image, and if we go to the &lt;em&gt;HTML Source&lt;/em&gt; tab, we will see the long image data string in the &lt;code&gt;img src&lt;/code&gt; attribute.&lt;/p&gt;
&lt;h3&gt;How to Send Multiple Emails&lt;/h3&gt;
&lt;p&gt;Sending multiple emails to different recipients and making them personal is the special thing about emails in Python.&lt;/p&gt;
&lt;p&gt;To add several more recipients, you can just type their addresses in separated by a comma, add &lt;code&gt;Cc&lt;/code&gt; and &lt;code&gt;Bcc&lt;/code&gt;. But if you work with a bulk email sending, Python will save you with loops.&lt;/p&gt;
&lt;p&gt;One of the options is to create a database in a &lt;em&gt;CSV&lt;/em&gt; format (we assume it is saved to the same folder as your Python script).&lt;/p&gt;
&lt;p&gt;We often see our names in transactional or even promotional examples. Here is how we can make it with Python.&lt;/p&gt;
&lt;p&gt;Let’s organize the list in a simple table with just two columns: name and email address. It should look like the following example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#name,email
John Johnson,john@johnson.com
Peter Peterson,peter@peterson.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code below will open the file and loop over its rows line by line, replacing the &lt;code&gt;{name}&lt;/code&gt; with the value from the “name” column.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import csv
import smtplib

port = 2525
smtp_server = &quot;smtp.mailtrap.io&quot;
login = &quot;1a2b3c4d5e6f7g&quot; # paste your login generated by Mailtrap
password = &quot;1a2b3c4d5e6f7g&quot; # paste your password generated by Mailtrap

message = &quot;&quot;&quot;Subject: Order confirmation
To: {recipient}
From: {sender}
Hi {name}, thanks for your order! We are processing it now and will contact you soon&quot;&quot;&quot;
sender = &quot;new@example.com&quot;
with smtplib.SMTP(&quot;smtp.mailtrap.io&quot;, 2525) as server:
  server.login(login, password)
  with open(&quot;contacts.csv&quot;) as file:
  reader = csv.reader(file)
  next(reader)  # it skips the header row
  for name, email in reader:
    server.sendmail(
      sender,
      email,
      message.format(name=name, recipient=email, sender=sender),
    )
    print(f&apos;Sent to {name}&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In our Mailtrap inbox, we see two messages: one for John Johnson and another for Peter Peterson, delivered simultaneously:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://lh3.googleusercontent.com/Q6fRy7tMexzLqSsfAwEDdkZVh7Onb4impsLJkqLs40HsuVo43JV0eAUjJiWvxf-L0t9vdoTgEfeiN3MYX0wBU0vUKVZCRbmstlHk2RqvQWnPqr9WJbMX7LciUO9ebj89B5UZLrLd&quot; alt=&quot;Screenshot of Mailtrap inbox showing two emails sent to different recipients&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;&lt;/h3&gt;
&lt;p&gt;Sending emails with Python via Gmail&lt;/p&gt;
&lt;p&gt;When you are ready for sending emails to real recipients, you can configure your production server. It also depends on your needs, goals, and preferences: your localhost or any external SMTP.&lt;/p&gt;
&lt;p&gt;One of the most popular options is Gmail so let’s take a closer look at it.&lt;/p&gt;
&lt;p&gt;We can often see titles like “How to set up a Gmail account for development”. In fact, it means that you will create a new Gmail account and will use it for a particular purpose.&lt;/p&gt;
&lt;p&gt;To be able to send emails via your Gmail account, you need to provide access to it for your application. You can &lt;a href=&quot;https://myaccount.google.com/lesssecureapps&quot;&gt;&lt;em&gt;Allow less secure apps&lt;/em&gt;&lt;/a&gt; or take advantage of the &lt;a href=&quot;https://developers.google.com/gmail/api/quickstart/python&quot;&gt;OAuth2 authorization protocol&lt;/a&gt;. It’s a way more difficult but recommended due to the security reasons.&lt;/p&gt;
&lt;p&gt;Further, to use a Gmail server, you need to know:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the server name = &lt;em&gt;smtp.gmail.com&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;port = &lt;em&gt;465 for SSL/TLS&lt;/em&gt; connection (preferred)&lt;/li&gt;
&lt;li&gt;or port = &lt;em&gt;587 for STARTTLS&lt;/em&gt; connection&lt;/li&gt;
&lt;li&gt;username = your Gmail email address&lt;/li&gt;
&lt;li&gt;password = your password&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import smtplib
import ssl

port = 465
password = input(&quot;your password&quot;)
context = ssl.create_default_context()

with smtplib.SMTP_SSL(&quot;smtp.gmail.com&quot;, port, context=context) as server:
  server.login(&quot;my@gmail.com&quot;, password)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you tend to simplicity, then you can use &lt;a href=&quot;https://pypi.org/project/yagmail/&quot;&gt;Yagmail&lt;/a&gt;, the dedicated Gmail/SMTP. It makes email sending really easy. Just compare the above examples with these several lines of code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import yagmail

yag = yagmail.SMTP()
contents = [
&quot;This is the body, and here is just text http://somedomain/image.png&quot;,
&quot;You can find an audio file attached.&quot;, &apos;/local/path/to/song.mp3&apos;
]
yag.send(&apos;to@someone.com&apos;, &apos;subject&apos;, contents)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Next steps with Python&lt;/h3&gt;
&lt;p&gt;Those are just basic options of sending emails with Python. To get great results, review the Python documentation and experiment with your own code!&lt;/p&gt;
&lt;p&gt;There are a bunch of various Python frameworks and libraries, which make creating apps more elegant and dedicated. In particular, some of them can help improve your experience with building emails sending functionality:&lt;/p&gt;
&lt;p&gt;The most popular frameworks are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Flask, which offers a simple interface for email sending: Flask Mail.&lt;/li&gt;
&lt;li&gt;Django, which can be a great option for building HTML templates.&lt;/li&gt;
&lt;li&gt;Zope comes in handy for a website development.&lt;/li&gt;
&lt;li&gt;Marrow Mailer is a dedicated mail delivery framework adding various helpful configurations.&lt;/li&gt;
&lt;li&gt;Plotly and its Dash can help with mailing graphs and reports.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Also, here is a &lt;a href=&quot;https://awesome-python.com/&quot;&gt;handy list&lt;/a&gt; of Python resources sorted by their functionality.&lt;/p&gt;
&lt;p&gt;Good luck and don’t forget to stay on the safe side when sending your emails!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;This article was originally published at Mailtrap’s blog: &lt;a href=&quot;https://blog.mailtrap.io/sending-emails-in-python-tutorial-with-code-examples/&quot;&gt;Sending emails with Python&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Python and fast HTTP clients</title><link>https://julien.danjou.info/blog/python-and-fast-http-clients/</link><guid isPermaLink="true">https://julien.danjou.info/blog/python-and-fast-http-clients/</guid><description>Nowadays, it is more than likely that you will have to write an HTTP client for your application that will have to talk to another HTTP server. The ubiquity of REST API makes HTTP a first class citize</description><pubDate>Mon, 07 Oct 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Nowadays, it is more than likely that you will have to write an HTTP client for your application that will have to talk to another HTTP server. The ubiquity of REST API makes HTTP a first class citizen. That&apos;s why knowing optimization patterns are a prerequisite.&lt;/p&gt;
&lt;p&gt;There are many HTTP clients in Python; the most widely used and easy to&lt;br /&gt;
work with is &lt;em&gt;&lt;a href=&quot;https://requests.kennethreitz.org/&quot;&gt;requests&lt;/a&gt;&lt;/em&gt;. It is the de-factor standard nowadays.&lt;/p&gt;
&lt;h2&gt;Persistent Connections&lt;/h2&gt;
&lt;p&gt;The first optimization to take into account is the use of a persistent connection to the Web server. Persistent connections are a standard since HTTP 1.1 though many applications do not leverage them. This lack of optimization is simple to explain if you know that when using &lt;em&gt;requests&lt;/em&gt; in its simple mode (e.g. with the &lt;code&gt;get&lt;/code&gt; function) the connection is closed on return. To avoid that, an application needs to use a &lt;code&gt;Session&lt;/code&gt; object that allows reusing an already opened connection.&lt;/p&gt;
&lt;p&gt;Each connection is stored in a pool of connections (10 by default), the size of&lt;br /&gt;
which is also configurable:&lt;/p&gt;
&lt;p&gt;Reusing the TCP connection to send out several HTTP requests offers a number of performance advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lower CPU and memory usage (fewer connections opened simultaneously).&lt;/li&gt;
&lt;li&gt;Reduced latency in subsequent requests (no TCP handshaking).&lt;/li&gt;
&lt;li&gt;Exceptions can be raised without the penalty of closing the TCP connection.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The HTTP protocol also provides &lt;a href=&quot;https://en.wikipedia.org/wiki/HTTP_pipelining&quot;&gt;pipelining&lt;/a&gt;, which allows sending several requests on the same connection without waiting for the replies to come (think batch). Unfortunately, this is not supported by the &lt;em&gt;requests&lt;/em&gt; library. However, pipelining requests may not be as fast as sending them in parallel. Indeed, the HTTP 1.1 protocol forces the replies to be sent in the same order as the requests were sent – first-in first-out.&lt;/p&gt;
&lt;h2&gt;Parallelism&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;requests&lt;/em&gt; also has one major drawback: it is synchronous. Calling &lt;code&gt;requests.get(&quot;http://example.org&quot;)&lt;/code&gt; blocks the program until the HTTP server replies completely. Having the application waiting and doing nothing can be a drawback here. It is possible that the program could do something else rather than sitting idle.&lt;/p&gt;
&lt;p&gt;A smart application can mitigate this problem by using a pool of threads like the ones provided by &lt;code&gt;concurrent.futures&lt;/code&gt;. It allows parallelizing the HTTP requests in a very rapid way.&lt;/p&gt;
&lt;p&gt;This pattern being quite useful, it has been packaged into a library named &lt;em&gt;&lt;a href=&quot;https://github.com/ross/requests-futures&quot;&gt;requests-futures&lt;/a&gt;&lt;/em&gt;. The usage of &lt;code&gt;Session&lt;/code&gt; objects is made transparent to the developer:&lt;/p&gt;
&lt;p&gt;By default a worker with two threads is created, but a program can easily customize this value by passing the &lt;code&gt;max_workers&lt;/code&gt; argument or even its own executor to the &lt;code&gt;FuturSession&lt;/code&gt; object – for example like this: &lt;code&gt;FuturesSession(executor=ThreadPoolExecutor(max_workers=10))&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Asynchronicity&lt;/h2&gt;
&lt;p&gt;As explained earlier, &lt;em&gt;requests&lt;/em&gt; is entirely synchronous. That blocks the application while waiting for the server to reply, slowing down the program. Making HTTP requests in threads is one solution, but threads do have their own overhead and this implies parallelism, which is not something everyone is always glad to see in a program.&lt;/p&gt;
&lt;p&gt;Starting with version 3.5, Python offers asynchronicity as its core using &lt;em&gt;asyncio&lt;/em&gt;. The &lt;a href=&quot;http://aiohttp.readthedocs.io/%5Baiohttp%5D&quot;&gt;aiohttp&lt;/a&gt; library provides an asynchronous HTTP client built on top of &lt;em&gt;asyncio&lt;/em&gt;. This library allows sending requests in series but without waiting for the first reply to come back before sending the new one. In contrast to HTTP pipelining, &lt;em&gt;aiohttp&lt;/em&gt; sends the requests over multiple connections in parallel, avoiding the ordering issue explained earlier.&lt;/p&gt;
&lt;p&gt;All those solutions (using &lt;code&gt;Session&lt;/code&gt;, &lt;em&gt;threads&lt;/em&gt;, &lt;em&gt;futures&lt;/em&gt; or &lt;em&gt;asyncio&lt;/em&gt;) offer different approaches to making HTTP clients faster.&lt;/p&gt;
&lt;h2&gt;Performances&lt;/h2&gt;
&lt;p&gt;The snippet below is an HTTP client sending requests to &lt;code&gt;httpbin.org&lt;/code&gt;, an HTTP API that provides (among other things) an endpoint simulating a long request (a second here). This example implements all the techniques listed above and times them.&lt;/p&gt;
&lt;p&gt;Running this program gives the following output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Time needed for `serialized&apos; called: 12.12s
Time needed for `Session&apos; called: 11.22s
Time needed for `FuturesSession w/ 2 workers&apos; called: 5.65s
Time needed for `FuturesSession w/ max workers&apos; called: 1.25s
Time needed for `aiohttp&apos; called: 1.19s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/07/20190716092338_hd.png&quot; alt=&quot;Benchmark chart comparing HTTP client performance in Python&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Without any surprise, the slower result comes with the dumb serialized version, since all the requests are made one after another without reusing the connection — 12 seconds to make 10 requests.&lt;/p&gt;
&lt;p&gt;Using a &lt;code&gt;Session&lt;/code&gt; object and therefore reusing the connection means saving 8% in terms of time, which is already a big and easy win. Minimally, you should always use a session.&lt;/p&gt;
&lt;p&gt;If your system and program allow the usage of threads, it is a good call to use them to parallelize the requests. However threads have some overhead, and they are not weight-less. They need to be created, started and then joined.&lt;/p&gt;
&lt;p&gt;Unless you are still using old versions of Python, without a doubt using &lt;em&gt;aiohttp&lt;/em&gt; should be the way to go nowadays if you want to write a fast and asynchronous HTTP client. It is the fastest and the most scalable solution as it can handle hundreds of parallel requests. The alternative, managing hundreds of threads in parallel is not a great option.&lt;/p&gt;
&lt;h2&gt;Streaming&lt;/h2&gt;
&lt;p&gt;Another speed optimization that can be efficient is streaming the requests. When making a request, by default the body of the response is downloaded immediately. The &lt;code&gt;stream&lt;/code&gt; parameter provided by the &lt;em&gt;requests&lt;/em&gt; library or the &lt;code&gt;content&lt;/code&gt; attribute for &lt;code&gt;aiohttp&lt;/code&gt; both provide a way to not load the full content in memory as soon as the request is executed.&lt;/p&gt;
&lt;p&gt;Not loading the full content is extremely important in order to avoid allocating potentially hundred of megabytes of memory for nothing. If your program does not need to access the entire content as a whole but can work on chunks, it is probably better to just use those methods. For example, if you&apos;re going to save and write the content to a file, reading only a chunk and writing it at the same time is going to be much more memory efficient than reading the whole HTTP body, allocating a giant pile of memory, and then writing it to disk.&lt;/p&gt;
&lt;p&gt;I hope that&apos;ll make it easier for you to write proper HTTP clients and requests. If you know any other useful technic or method, feel free to write it down in the comment section below!&lt;/p&gt;
</content:encoded></item><item><title>Dependencies Handling in Python</title><link>https://julien.danjou.info/blog/dependencies-handling-in-python-automatic-update/</link><guid isPermaLink="true">https://julien.danjou.info/blog/dependencies-handling-in-python-automatic-update/</guid><description>Dependencies are a nightmare. Here&apos;s how to handle them properly in Python with pipenv, poetry, Dependabot, and Mergify for fully automatic updates.</description><pubDate>Mon, 02 Sep 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Dependencies are a nightmare for many people. &lt;a href=&quot;https://thenewstack.io/to-reduce-tech-debt-eliminate-dependencies-and-refactoring/&quot;&gt;Some even argue they are technical debt&lt;/a&gt;. Managing the list of the libraries of your software is a horrible experience. Updating them — automatically? — sounds like a delirium.&lt;/p&gt;
&lt;p&gt;Stick with me here as I am going to help you get a better grasp on something that you cannot, in practice, get rid of — unless you&apos;re incredibly rich and talented and can live without the code of others.&lt;/p&gt;
&lt;p&gt;First, we need to be clear of something about dependencies: there are two types of them. &lt;a href=&quot;https://caremad.io/posts/2013/07/setup-vs-requirement/&quot;&gt;Donald Stuff wrote better than I would about the subject&lt;/a&gt; years ago. To make it simple, one can say that they are two types of code packages depending on  external code: applications and libraries.&lt;/p&gt;
&lt;h3&gt;Libraries Dependencies&lt;/h3&gt;
&lt;p&gt;Python libraries should specify their dependencies in a generic way. A library should not require &lt;code&gt;requests 2.1.5&lt;/code&gt;: it does not make sense. If every library out there needs a different version of &lt;code&gt;requests&lt;/code&gt;, they can&apos;t be used at the same time.&lt;/p&gt;
&lt;p&gt;Libraries need to declare dependencies based on ranges of version numbers. Requiring &lt;code&gt;requests&amp;gt;=2&lt;/code&gt; is correct. Requiring &lt;code&gt;requests&amp;gt;=1,&amp;lt;2&lt;/code&gt; is also correct if you know that &lt;code&gt;requests 2.x&lt;/code&gt; does not work with the library. The problem that your version range specification is solving is the &lt;strong&gt;API compatibility issue&lt;/strong&gt; between your code and your dependencies — &lt;em&gt;nothing else&lt;/em&gt;. That&apos;s a good reason for libraries to use &lt;a href=&quot;https://semver.org/&quot;&gt;Semantic Versioning&lt;/a&gt; whenever possible.&lt;/p&gt;
&lt;p&gt;Therefore, dependencies should be written in &lt;code&gt;setup.py&lt;/code&gt; as something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from setuptools import setup

setup(
    name=&quot;MyLibrary&quot;,
    version=&quot;1.0&quot;,
    install_requires=[
        &quot;requests&quot;,
    ],
    # ...
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, it is easy for any application to use the library and co-exist with others.&lt;/p&gt;
&lt;h3&gt;Applications Dependencies&lt;/h3&gt;
&lt;p&gt;An application is just a particular case of libraries. They are not intended to be reused (imported) by other libraries of applications — though nothing would prevent it in practice.&lt;/p&gt;
&lt;p&gt;In the end, that means that you should specify the dependencies the same way that you would do for a library in the application&apos;s &lt;code&gt;setup.py&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The main difference is that an application is usually &lt;em&gt;deployed&lt;/em&gt; in production to provide its service. Deployments need to be reproducible. For that, you can&apos;t solely rely on &lt;code&gt;setup.py&lt;/code&gt;: the requested range of the dependencies are too broad. You&apos;re at the mercy of random version changes at any time when re-deploying your application.&lt;/p&gt;
&lt;p&gt;You, therefore, need a different version management mechanism to handle deployment than just &lt;code&gt;setup.py&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;pipenv&lt;/em&gt; has &lt;a href=&quot;https://docs.pipenv.org/en/latest/advanced/#pipfile-vs-setuppy&quot;&gt;an excellent section recapping this&lt;/a&gt; in its documentation. It splits dependency types into &lt;em&gt;abstract&lt;/em&gt; and &lt;em&gt;concrete&lt;/em&gt; dependencies: &lt;em&gt;abstract&lt;/em&gt; dependencies are based on ranges (e.g., libraries) whereas &lt;em&gt;concrete&lt;/em&gt; dependencies are specified with precise versions (e.g., application deployments) — as we&apos;ve just seen here.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Handling Deployment&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;requirements.txt&lt;/code&gt; file has been used to solve application deployment reproducibility for a long time now. Its format is usually something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;requests==3.1.5
foobar==2.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each library sees itself specified to the micro version. That makes sure each of your deployment is going to install the same version of your dependency. Using a &lt;code&gt;requirements.txt&lt;/code&gt; is a simple solution and a first step toward reproducible deployment. However, it&apos;s not &lt;em&gt;enough&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Indeed, while you can specify which version of &lt;code&gt;requests&lt;/code&gt; you want, if &lt;code&gt;requests&lt;/code&gt; depends on &lt;code&gt;urllib3&lt;/code&gt;, that could make &lt;code&gt;pip&lt;/code&gt; install &lt;code&gt;urllib 2.1&lt;/code&gt; or &lt;code&gt;urllib 2.2&lt;/code&gt;. You can&apos;t know which one will be installed, which does not make your deployment 100% reproducible.&lt;/p&gt;
&lt;p&gt;Of course, you &lt;em&gt;could&lt;/em&gt; duplicate all &lt;code&gt;requests&lt;/code&gt; dependencies yourself in your &lt;code&gt;requirements.txt&lt;/code&gt;, but that would be &lt;strong&gt;madness&lt;/strong&gt;!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/08/image.png&quot; alt=&quot;An application dependency tree can be quite deep and complex sometimes.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;There are various hacks available to fix this limitation, but the real saviors here are &lt;a href=&quot;https://github.com/pypa/pipenv&quot;&gt;&lt;em&gt;pipenv&lt;/em&gt;&lt;/a&gt; and &lt;a href=&quot;https://poetry.eustace.io/&quot;&gt;&lt;em&gt;poetry&lt;/em&gt;&lt;/a&gt;. The way they solve it is similar to many package managers in other programming languages. They generate a &lt;em&gt;lock file&lt;/em&gt; that contains the list of all installed dependencies (and their own dependencies, etc.) with their version numbers. That makes sure the deployment is 100% reproducible.&lt;/p&gt;
&lt;p&gt;Check out their documentation on how to set up and use them!&lt;/p&gt;
&lt;h3&gt;Handling Dependencies Updates&lt;/h3&gt;
&lt;p&gt;Now that you have your &lt;em&gt;lock file&lt;/em&gt; that makes sure your deployment is reproducible in a snap, you&apos;ve another problem. How do you make sure that your dependencies are up-to-date? There is a real security concern about this, but also bug fixes and optimizations that you might miss by staying behind.&lt;/p&gt;
&lt;p&gt;If your project is hosted on &lt;a href=&quot;https://github.com&quot;&gt;GitHub&lt;/a&gt;, &lt;a href=&quot;https://dependabot.com/&quot;&gt;Dependabot&lt;/a&gt; is an excellent solution to solve this issue. Enabling this application on your repository creates automatically pull requests whenever a new version of the library listed in your lock file is available. For example, if you&apos;ve deployed your application with &lt;code&gt;redis 3.3.6&lt;/code&gt;, Dependabot will create a pull request updating to &lt;code&gt;redis 3.3.7&lt;/code&gt; as soon as it gets released. Furthermore, Dependabot supports &lt;code&gt;requirements.txt&lt;/code&gt;, &lt;em&gt;pipenv&lt;/em&gt;, and &lt;em&gt;poetry&lt;/em&gt;!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/08/Screenshot-2019-08-14-at-17.57.47.png&quot; alt=&quot;Dependabot updating jinja2 for you&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Automatic Deployment Update&lt;/h2&gt;
&lt;p&gt;You&apos;re almost there. You have a bot that is letting you know that a new version of a library your project needs is available.&lt;/p&gt;
&lt;p&gt;Once the pull request is created, your continuous integration system is going to kick in, deploy your project, and runs the test. If everything works fine, your pull request is ready to be merged. But are &lt;em&gt;you&lt;/em&gt; really needed in this process?&lt;/p&gt;
&lt;p&gt;Unless you have a particular and personal aversion on specific version numbers —&quot;Gosh I hate versions that end with a 3. It&apos;s always bad luck.&quot;— or unless you have zero automated testing, you, human, is useless. This merge can be fully automatic.&lt;/p&gt;
&lt;p&gt;This is where &lt;a href=&quot;https://mergify.io&quot;&gt;&lt;em&gt;Mergify&lt;/em&gt;&lt;/a&gt; comes into play. Mergify is a GitHub application allowing to define precise rules about how to merge your pull requests. Here&apos;s a rule that I use in every project:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pull_requests_rules:
  - name: automatic merge from dependabot
    conditions:
      - author~=^dependabot(|-preview)\[bot\]$
      - label!=work-in-progress
      - &quot;status-success=ci/circleci: pep8&quot;
      - &quot;status-success=ci/circleci: py37&quot;
    actions:
      merge:
        method: merge
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/08/Screenshot-2019-08-14-at-18.38.25.png&quot; alt=&quot;Mergify reports when the rule fully matches&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As soon as your continuous integration system passes, Mergify merges the pull request for you.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/08/Screenshot-2019-08-14-at-18.38.37.png&quot; alt=&quot;Screenshot of Mergify automatically merging a Dependabot pull request&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You can then automatically trigger your deployment hooks to update your production deployment and get the new library version installed right away. This leaves your application always up-to-date with newer libraries and not lagging behind several years of releases.&lt;/p&gt;
&lt;p&gt;If anything goes wrong, you&apos;re still able to revert the commit from Dependabot — which you can also automate if you wish with a Mergify rule.&lt;/p&gt;
&lt;h2&gt;Beyond&lt;/h2&gt;
&lt;p&gt;This is to me the state of the art of dependency management lifecycle right now. And while this applies exceptionally well to Python, it can be applied to many other languages that use a similar pattern — such as Node and &lt;em&gt;npm&lt;/em&gt;.&lt;/p&gt;
</content:encoded></item><item><title>The Art of PostgreSQL is out!</title><link>https://julien.danjou.info/blog/the-art-of-postgresql-is-out/</link><guid isPermaLink="true">https://julien.danjou.info/blog/the-art-of-postgresql-is-out/</guid><description>If you remember well, a couple of years ago, I wrote about Mastering PostgreSQL, a fantastic book written by my friend Dimitri Fontaine.  Dimitri is a long-time PostgreSQL core developer — for example</description><pubDate>Wed, 28 Aug 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If you remember well, a couple of years ago, I wrote about &lt;em&gt;Mastering PostgreSQL&lt;/em&gt;, a fantastic book written by my friend Dimitri Fontaine.&lt;/p&gt;
&lt;p&gt;Dimitri is a long-time PostgreSQL core developer — for example, he wrote the extension support in PostgreSQL — no less. He is featured in my book &lt;a href=&quot;https://serious-python.com&quot;&gt;Serious Python&lt;/a&gt;, where he advises on using databases and ORM in Python.&lt;/p&gt;
&lt;p&gt;Today, Dimitri comes back with the new version of this book, named &lt;em&gt;&lt;a href=&quot;https://jdanjou--theartofpostgresql.thrivecart.com/full-edition/?ref=blog&quot;&gt;The Art of PostgreSQL&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/08/IMG_20141121_164610.jpg&quot; alt=&quot;As a bonus, here&apos;s a picture of me and Dimitri having fun in a PostgreSQL meetup!&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I love the motto of this book: &lt;em&gt;Turn Thousands of Lines of Code into Simple Queries&lt;/em&gt;. I have spent all my career working with code that talks to databases, and I can&apos;t count the number of times where I&apos;ve seen people write lengthy, slow code in their pet language rather than a single well-thought SQL query which would do a better job.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/08/image-5.png&quot; alt=&quot;Cover of The Art of PostgreSQL&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This is exactly what &lt;a href=&quot;https://jdanjou--theartofpostgresql.thrivecart.com/full-edition/?ref=blog&quot;&gt;this book&lt;/a&gt; is about.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That&apos;s why it&apos;s my favorite SQL book. I learned so many things from it. In many cases, I&apos;ve been able to divide by 10 the size of the code I had to write in Python to implement a feature. All I had to do is to browse the book to discover the right PostgreSQL feature and write a single SQL query. The &lt;em&gt;right&lt;/em&gt; query that does the job for me_._&lt;/p&gt;
&lt;p&gt;Less code, fewer bugs, more happiness!&lt;/p&gt;
&lt;p&gt;The book also features interviews with great PostgreSQL users and developers — hey, no wonder where Dimitri got this idea, right? ;-)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/08/Screenshot-2019-08-28-at-15.09.21.png&quot; alt=&quot;Screenshot of interview excerpts featured in The Art of PostgreSQL&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I loved those interviews. What&apos;s better than reading Kris Jenkins explaining how Clojure and PostgreSQL play nice together, or Markus Winand (from the famous &lt;a href=&quot;https://use-the-index-luke.com/&quot;&gt;use-the-index-luke.com&lt;/a&gt;) talking about the relationship developers have with their database. :-)&lt;/p&gt;
&lt;p&gt;No need to say that you should get your hands on this &lt;strong&gt;right now.&lt;/strong&gt; Dimitri just made a launch offer where &lt;a href=&quot;https://jdanjou--theartofpostgresql.thrivecart.com/full-edition/?ref=blog&quot;&gt;he offers a &lt;strong&gt;15% discount&lt;/strong&gt; on the book&lt;/a&gt; until the end of this month! You can also &lt;a href=&quot;https://jdanjou--theartofpostgresql.thrivecart.com/full-edition/?ref=blog&quot;&gt;read the free chapter&lt;/a&gt; to get an idea of what you&apos;ll get.&lt;/p&gt;
&lt;p&gt;Last thing: it&apos;s DRM-free and money-back guaranteed. You can &lt;a href=&quot;https://jdanjou--theartofpostgresql.thrivecart.com/full-edition/?ref=blog&quot;&gt;get this book&lt;/a&gt; with your eyes closed.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/08/Screenshot-2019-08-28-at-15.25.14.png&quot; alt=&quot;Screenshot of The Art of PostgreSQL book packages and pricing&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>Handling multipart/form-data natively in Python</title><link>https://julien.danjou.info/blog/handling-multipart-form-data-python/</link><guid isPermaLink="true">https://julien.danjou.info/blog/handling-multipart-form-data-python/</guid><description>RFC7578 (who obsoletes RFC2388) defines the multipart/form-data type that is usually transported over HTTP when users submit forms on your Web page.</description><pubDate>Mon, 01 Jul 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://tools.ietf.org/html/rfc7578&quot;&gt;RFC7578&lt;/a&gt; (who obsoletes &lt;a href=&quot;https://tools.ietf.org/html/rfc2388&quot;&gt;RFC2388&lt;/a&gt;) defines the &lt;code&gt;multipart/form-data&lt;/code&gt; type that is usually transported over HTTP when users submit forms on your Web page. Nowadays, it tends to be replaced by JSON encoded payloads; nevertheless, it is still widely used.&lt;/p&gt;
&lt;p&gt;While you could decode an HTTP body request made with JSON natively with Python — thanks to the &lt;code&gt;json&lt;/code&gt; module — there is no such way to do that with &lt;code&gt;multipart/form-data&lt;/code&gt;. That&apos;s something barely understandable considering how old the format is.&lt;/p&gt;
&lt;p&gt;There is a wide variety of way available to encode and decode this format. Libraries such as &lt;em&gt;requests&lt;/em&gt; support this natively without making you notice, and the same goes for the majority of Web server frameworks such as &lt;em&gt;Django&lt;/em&gt; or &lt;em&gt;Flask&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;However, in certain circumstances, you might be on your own to encode or decode this format, and it might not be an option to pull (significant) dependencies.&lt;/p&gt;
&lt;h2&gt;Encoding&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;multipart/form-data&lt;/code&gt; format is quite simple to understand and can be summarised as an easy way to encode a list of keys and values, i.e., a portable way of serializing a dictionary.&lt;/p&gt;
&lt;p&gt;There&apos;s nothing in Python to generate such an encoding. The format is quite simple and consists of the key and value surrounded by a random boundary delimiter. This delimiter must be passed as part of the &lt;code&gt;Content-Type&lt;/code&gt;, so that the decoder can decode the form data.&lt;/p&gt;
&lt;p&gt;There&apos;s a simple implementation in &lt;em&gt;urllib3&lt;/em&gt; that does the job. It&apos;s possible to summarize it in this simple implementation:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import binascii
import os

def encode_multipart_formdata(fields):
    boundary = binascii.hexlify(os.urandom(16)).decode(&apos;ascii&apos;)

    body = (
        &quot;&quot;.join(&quot;--%s\r\n&quot;
                &quot;Content-Disposition: form-data; name=\&quot;%s\&quot;\r\n&quot;
                &quot;\r\n&quot;
                &quot;%s\r\n&quot; % (boundary, field, value)
                for field, value in fields.items()) +
        &quot;--%s--\r\n&quot; % boundary
    )

    content_type = &quot;multipart/form-data; boundary=%s&quot; % boundary

    return body, content_type
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can use by passing a dictionary where keys and values are bytes. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;encode_multipart_formdata({&quot;foo&quot;: &quot;bar&quot;, &quot;name&quot;: &quot;jd&quot;})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which returns:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;--00252461d3ab8ff5c25834e0bffd6f70
Content-Disposition: form-data; name=&quot;foo&quot;

bar
--00252461d3ab8ff5c25834e0bffd6f70
Content-Disposition: form-data; name=&quot;name&quot;

jd
--00252461d3ab8ff5c25834e0bffd6f70--
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;multipart/form-data; boundary=00252461d3ab8ff5c25834e0bffd6f70
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can use the returned content type in your HTTP reply header &lt;code&gt;Content-Type&lt;/code&gt;. Note that this format is used for forms: it can also be used by emails.&lt;/p&gt;
&lt;p&gt;Emails did you say?&lt;/p&gt;
&lt;h2&gt;Encoding with &lt;code&gt;email&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Right, emails are usually encoded using MIME, which is defined by yet another RFC, &lt;a href=&quot;https://tools.ietf.org/html/rfc2046&quot;&gt;RFC2046&lt;/a&gt;. It turns out that &lt;code&gt;multipart/form-data&lt;/code&gt; is just a particular MIME format, and that if you have code that implements MIME handling, it&apos;s easy to use it to implement this format.&lt;/p&gt;
&lt;p&gt;Fortunately for us, Python standard library comes with a module that handles exactly that: &lt;code&gt;email.mime&lt;/code&gt;. I told you it was heavily used by email — I guess that&apos;s why they put that code in the &lt;code&gt;email&lt;/code&gt; subpackage.&lt;/p&gt;
&lt;p&gt;Here&apos;s a piece of code that handles &lt;code&gt;multipart/form-data&lt;/code&gt; in a few lines of code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from email import message
from email.mime import multipart
from email.mime import nonmultipart
from email.mime import text

class MIMEFormdata(nonmultipart.MIMENonMultipart):
    def __init__(self, keyname, *args, **kwargs):
        super(MIMEFormdata, self).__init__(*args, **kwargs)
        self.add_header(
            &quot;Content-Disposition&quot;, &quot;form-data; name=\&quot;%s\&quot;&quot; % keyname)

def encode_multipart_formdata(fields):
    m = multipart.MIMEMultipart(&quot;form-data&quot;)

    for field, value in fields.items():
        data = MIMEFormdata(field, &quot;text&quot;, &quot;plain&quot;)
        data.set_payload(value)
        m.attach(data)

    return m
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using this piece of code returns the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Content-Type: multipart/form-data; boundary=&quot;===============1107021068307284864==&quot;
MIME-Version: 1.0

--===============1107021068307284864==
Content-Type: text/plain
MIME-Version: 1.0
Content-Disposition: form-data; name=&quot;foo&quot;

bar
--===============1107021068307284864==
Content-Type: text/plain
MIME-Version: 1.0
Content-Disposition: form-data; name=&quot;name&quot;

jd
--===============1107021068307284864==--
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This method has several advantages over our first implementation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It handles &lt;code&gt;Content-Type&lt;/code&gt; for each of the added MIME parts. We could add other data types than just &lt;code&gt;text/plain&lt;/code&gt; like it is implicitly done in the first version. We could also specify the charset (encoding) of the textual data.&lt;/li&gt;
&lt;li&gt;It&apos;s very likely more robust by leveraging the wildly tested Python standard library.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The main downside, in that case, is that the &lt;code&gt;Content-Type&lt;/code&gt; header is included with the content. In case of handling HTTP, it is problematic as this needs to be sent as part of the HTTP header and not as part of the payload.&lt;/p&gt;
&lt;p&gt;It should be possible to build a particular generator from &lt;code&gt;email.generator&lt;/code&gt; that does this. I&apos;ll leave that as an exercise to you, reader.&lt;/p&gt;
&lt;h2&gt;Decoding&lt;/h2&gt;
&lt;p&gt;We must be able to use that same &lt;code&gt;email&lt;/code&gt; package to decode our encoded data, right? It turns out that&apos;s the case, with a piece of code that looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import email.parser

msg = email.parser.BytesParser().parsebytes(my_multipart_data)

print({
    part.get_param(&apos;name&apos;, header=&apos;content-disposition&apos;): part.get_payload(decode=True)
    for part in msg.get_payload()
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the example data above, this returns:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{&apos;foo&apos;: b&apos;bar&apos;, &apos;name&apos;: b&apos;jd&apos;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Amazing, right?&lt;/p&gt;
&lt;p&gt;The moral of this story is that you should never underestimate the power of the standard library. While it&apos;s easy to add a single line in your list of dependencies, it&apos;s not always required if you dig a bit into what Python provides for you!&lt;/p&gt;
</content:encoded></item><item><title>Advanced Functional Programming in Python: lambda</title><link>https://julien.danjou.info/blog/python-functional-programming-lambda/</link><guid isPermaLink="true">https://julien.danjou.info/blog/python-functional-programming-lambda/</guid><description>A few weeks ago, I introduced you to functional programming in Python. Today, I&apos;d like to go further into this topic and show you so more interesting features.</description><pubDate>Mon, 03 Jun 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few weeks ago, I introduced you to &lt;a href=&quot;https://julien.danjou.info/blog/python-and-functional-programming&quot;&gt;functional programming in Python&lt;/a&gt;. Today, I&apos;d like to go further into this topic and show you so more interesting features.&lt;/p&gt;
&lt;h2&gt;Lambda Functions&lt;/h2&gt;
&lt;p&gt;What do we call lambda functions? They are in essence anonymous functions. In order to create them, you must use the &lt;code&gt;lambda&lt;/code&gt; statement:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; lambda x: x
&amp;lt;function &amp;lt;lambda&amp;gt; at 0x102e23620&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In Python, lambda functions are quite limited. They can take any number of arguments; however they can contain only one statement and be written on a single line.&lt;/p&gt;
&lt;p&gt;They are mostly useful to be passed to high-order functions, such as &lt;code&gt;map()&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; list(map(lambda x: x * 2, range(10)))
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will apply the anonymous function &lt;code&gt;lambda x: x * 2&lt;/code&gt; to every item returned by &lt;code&gt;range(10)&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;functools.partial&lt;/h2&gt;
&lt;p&gt;Since lambda functions are limited to being one line long, it&apos;s often that they are used to &lt;em&gt;specialize&lt;/em&gt; longer version of an existing function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def between(number, min=0, max=1000):
    return max &amp;gt; number &amp;gt; min

## Only returns number between 10 and 1000
filter(lambda x: between(x, min=10), range(10000))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our lambda is finally just a wrapper of the &lt;code&gt;between&lt;/code&gt; function with one of the argument already set. What if we would have a better way, without the various lambda limitations, to write that? That&apos;s where &lt;code&gt;functools.partial&lt;/code&gt; comes handy.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import functools
def between(number, min=0, max=1000):
    return max &amp;gt; number &amp;gt; min

## Only returns number between 10 and 1000
atleast_10_and_upto = functools.partial(between, min=10)
## Return number betweens 10 and 1000
filter(atleast_10_and_upto, range(10000))

## Return number betweens 10 and 20
filter(lambda x: atleast_10_and_upto(x, max=20), range(10000))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;functools.partial&lt;/code&gt; function returns a specialized version of the &lt;code&gt;between&lt;/code&gt; function, where &lt;code&gt;min&lt;/code&gt; is already set. We can store them in a variable, use it, reuse it, as much as we want. We can pass it a &lt;code&gt;max&lt;/code&gt; argument, as shown in the second part — using a &lt;code&gt;lambda&lt;/code&gt;! You can mix and matches those two as you prefer and what seems clearer for you.&lt;/p&gt;
&lt;h2&gt;Common lambda&lt;/h2&gt;
&lt;p&gt;There is a type of lambda function that is pretty common: the attribute or item getter. They are typically used a &lt;code&gt;key&lt;/code&gt; function for sorting or filtering.&lt;/p&gt;
&lt;p&gt;Here&apos;s a list of 200 tuples containing two integers &lt;code&gt;(i1, i2)&lt;/code&gt;. If you want to use only &lt;code&gt;i2&lt;/code&gt; as the sorting key, you would write:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mylist = list(zip(range(40, 240), range(-100, 100)))

sorted(mylist, key=lambda i: i[1])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which works fine, but make you use &lt;code&gt;lambda&lt;/code&gt;. You could rather use the &lt;code&gt;operator&lt;/code&gt; module:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import operator

mylist = list(zip(range(40, 240), range(-100, 100)))

sorted(mylist, key=operator.itemgetter(1))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This does the same thing, except it avoids using &lt;code&gt;lambda&lt;/code&gt; altogether. Cherry-on-the-cake: it is actually 10% faster on my laptop.&lt;/p&gt;
&lt;p&gt;I hope that&apos;ll make you write more functional code!&lt;/p&gt;
</content:encoded></item><item><title>An Introduction to Functional Programming with Python</title><link>https://julien.danjou.info/blog/python-and-functional-programming/</link><guid isPermaLink="true">https://julien.danjou.info/blog/python-and-functional-programming/</guid><description>Many Python developers are unaware of the extent to which you can use functional programming in Python, which is a shame: with few exceptions, functional programming allows you to write more concise a</description><pubDate>Mon, 06 May 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Many Python developers are unaware of the extent to which you can use functional programming in Python, which is a shame: with few exceptions, functional programming allows you to write more concise and efficient code. Moreover, Python’s support for functional programming is extensive.&lt;/p&gt;
&lt;p&gt;Here I&apos;d like to talk a bit about how you can actually have a functional approach to programming with our favorite language.&lt;/p&gt;
&lt;h2&gt;Pure Functions&lt;/h2&gt;
&lt;p&gt;When you write code using a functional style, your functions are designed to have no side effects: instead, they take an input and produce an output without keeping state or modifying anything not reflected in the return value. Functions that follow this ideal are referred to as purely functional.&lt;/p&gt;
&lt;p&gt;Let’s start with an example of a regular, non-pure function that removes the last item in a list:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def remove_last_item(mylist):
    &quot;&quot;&quot;Removes the last item from a list.&quot;&quot;&quot;
    mylist.pop(-1)  # This modifies mylist
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function is not pure: it has a side effect as it modifies the argument it is given. Let&apos;s rewrite it as purely functional:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def butlast(mylist):
    &quot;&quot;&quot;Like butlast in Lisp; returns the list without the last element.&quot;&quot;&quot;
    return mylist[:-1]  # This returns a copy of mylist
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We define a &lt;code&gt;butlast()&lt;/code&gt; function (like &lt;code&gt;butlast&lt;/code&gt; in Lisp) that returns the list without the last element without modifying the original list. Instead, it returns a copy of the list that has the modifications in place, allowing us to keep the original. The practical advantages of using functional programming include the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Modularity.&lt;/em&gt; Writing with a functional style forces a certain degree of&lt;br /&gt;
separation in solving your individual problems and makes sections of code&lt;br /&gt;
easier to reuse in other contexts. Since the function does not depend on any&lt;br /&gt;
external variable or state, call it from a different piece of code is&lt;br /&gt;
straightforward.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Brevity.&lt;/em&gt; Functional programming is often less verbose than other paradigms.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Concurrency.&lt;/em&gt; Purely functional functions are thread-safe and can run&lt;br /&gt;
concurrently. Some functional languages do this automatically, which can be&lt;br /&gt;
a big help if you ever need to scale your application, though this is not&lt;br /&gt;
quite the case yet in Python.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Testability.&lt;/em&gt; Testing a functional program is incredibly easy: all you need&lt;br /&gt;
is a set of inputs and an expected set of outputs. They are idempotent,&lt;br /&gt;
meaning that calling the same function over and over with the same arguments&lt;br /&gt;
will always return the same result.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that concepts such as &lt;a href=&quot;https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions&quot;&gt;list comprehension&lt;/a&gt; in Python are already functionals in their approach, as they are designed to avoid side effects. We&apos;ll see in the following that some of the functional functions Python provide can actually be expressed as list comprehension!&lt;/p&gt;
&lt;h2&gt;Python Functional Functions&lt;/h2&gt;
&lt;p&gt;You might repeatedly encounter the same set of problems when manipulating data using functional programming. To help you deal with this situation efficiently, Python includes a number of functions for functional programming. Here, we&apos;ll see with a quick overview some of these built-in functions that allows you to build fully functional programs. Once you have an idea of what’s available, I encourage you to research further and try out functions where they might apply in your own code.&lt;/p&gt;
&lt;h3&gt;Applying Functions to Items with &lt;code&gt;map&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;map()&lt;/code&gt; function takes the form &lt;code&gt;map(function, iterable)&lt;/code&gt; and applies &lt;code&gt;function&lt;/code&gt; to each item in &lt;code&gt;iterable&lt;/code&gt; to return an iterable map object:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; map(lambda x: x + &quot;bzz!&quot;, [&quot;I think&quot;, &quot;I&apos;m good&quot;])
&amp;lt;map object at 0x7fe7101abdd0&amp;gt;
&amp;gt;&amp;gt;&amp;gt; list(map(lambda x: x + &quot;bzz!&quot;, [&quot;I think&quot;, &quot;I&apos;m good&quot;]))
[&apos;I thinkbzz!&apos;, &quot;I&apos;m goodbzz!&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You could also write an equivalent of &lt;code&gt;map()&lt;/code&gt; using list comprehension, which&lt;br /&gt;
would look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; (x + &quot;bzz!&quot; for x in [&quot;I think&quot;, &quot;I&apos;m good&quot;])
&amp;lt;generator object &amp;lt;genexpr&amp;gt; at 0x7f9a0d697dc0&amp;gt;
&amp;gt;&amp;gt;&amp;gt; [x + &quot;bzz!&quot; for x in [&quot;I think&quot;, &quot;I&apos;m good&quot;]]
[&apos;I thinkbzz!&apos;, &quot;I&apos;m goodbzz!&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Filtering Lists with &lt;code&gt;filter&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;filter()&lt;/code&gt; function takes the form &lt;code&gt;filter(function or None, iterable)&lt;/code&gt; and filters the items in iterable based on the result returned by &lt;code&gt;function&lt;/code&gt;. This will return iterable &lt;code&gt;filter&lt;/code&gt; object:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; filter(lambda x: x.startswith(&quot;I &quot;), [&quot;I think&quot;, &quot;I&apos;m good&quot;])
&amp;lt;filter object at 0x7f9a0d636dd0&amp;gt;
&amp;gt;&amp;gt;&amp;gt; list(filter(lambda x: x.startswith(&quot;I &quot;), [&quot;I think&quot;, &quot;I&apos;m good&quot;]))
[&apos;I think&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You could also write an equivalent of &lt;code&gt;filter()&lt;/code&gt; using list comprehension, like&lt;br /&gt;
so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; (x for x in [&quot;I think&quot;, &quot;I&apos;m good&quot;] if x.startswith(&quot;I &quot;))
&amp;lt;generator object &amp;lt;genexpr&amp;gt; at 0x7f9a0d697dc0&amp;gt;
&amp;gt;&amp;gt;&amp;gt; [x for x in [&quot;I think&quot;, &quot;I&apos;m good&quot;] if x.startswith(&quot;I &quot;)]
[&apos;I think&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Getting Indexes with &lt;code&gt;enumerate&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;enumerate()&lt;/code&gt; function takes the form &lt;code&gt;enumerate(iterable[, start])&lt;/code&gt; and returns an iterable object that provides a sequence of tuples, each consisting of an integer index (starting with &lt;code&gt;start&lt;/code&gt;, if provided) and the corresponding item in &lt;code&gt;iterable&lt;/code&gt;. This function is useful when you need to write code that refers to array indexes. For example, instead of writing this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;i = 0
while i &amp;lt; len(mylist):
    print(&quot;Item %d: %s&quot; % (i, mylist[i]))
    i += 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You could accomplish the same thing more efficiently with &lt;code&gt;enumerate()&lt;/code&gt;, like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for i, item in enumerate(mylist):
    print(&quot;Item %d: %s&quot; % (i, item))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Sorting a List with &lt;code&gt;sorted&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;sorted()&lt;/code&gt; function takes the form &lt;code&gt;sorted(iterable, key=None, reverse=False)&lt;/code&gt; and returns a sorted version of &lt;code&gt;iterable&lt;/code&gt;. The key argument allows you to provide a function that returns the value to sort on:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; sorted([(&quot;a&quot;, 2), (&quot;c&quot;, 1), (&quot;d&quot;, 4)])
[(&apos;a&apos;, 2), (&apos;c&apos;, 1), (&apos;d&apos;, 4)]
&amp;gt;&amp;gt;&amp;gt; sorted([(&quot;a&quot;, 2), (&quot;c&quot;, 1), (&quot;d&quot;, 4)], key=lambda x: x[1])
[(&apos;c&apos;, 1), (&apos;a&apos;, 2), (&apos;d&apos;, 4)]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Finding Items That Satisfy Conditions with any and all&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;any(iterable)&lt;/code&gt; and &lt;code&gt;all(iterable)&lt;/code&gt; functions both return a Boolean depending on the values returned by &lt;code&gt;iterable&lt;/code&gt;. These simple functions are equivalent to the following full Python code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def all(iterable):
    for x in iterable:
        if not x:
            return False
    return True

def any(iterable):
    for x in iterable:
        if x:
            return True
    return False
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These functions are useful for checking whether any or all of the values in an iterable satisfy a given condition. For example, the following checks a list for two conditions:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mylist = [0, 1, 3, -1]
if all(map(lambda x: x &amp;gt; 0, mylist)):
    print(&quot;All items are greater than 0&quot;)
if any(map(lambda x: x &amp;gt; 0, mylist)):
    print(&quot;At least one item is greater than 0&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key difference here, as you can see, is that &lt;code&gt;any()&lt;/code&gt; returns &lt;code&gt;True&lt;/code&gt; when at least one element meets the condition, while &lt;code&gt;all()&lt;/code&gt; returns &lt;code&gt;True&lt;/code&gt; only if every element meets the condition. The &lt;code&gt;all()&lt;/code&gt; function will also return &lt;code&gt;True&lt;/code&gt; for an empty iterable, since none of the elements is &lt;code&gt;False&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Combining Lists with &lt;code&gt;zip&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;zip()&lt;/code&gt; function takes the form &lt;code&gt;zip(iter1 [,iter2 [...]])&lt;/code&gt; and takes multiple sequences and combines them into tuples. This is useful when you need to combine a list of keys and a list of values into a &lt;code&gt;dict&lt;/code&gt;. Like the other functions described here, &lt;code&gt;zip()&lt;/code&gt; returns an iterable. Here we have a list of keys that we map to a list of values to create a dictionary:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; keys = [&quot;foobar&quot;, &quot;barzz&quot;, &quot;ba!&quot;]
&amp;gt;&amp;gt;&amp;gt; map(len, keys)
&amp;lt;map object at 0x7fc1686100d0&amp;gt;
&amp;gt;&amp;gt;&amp;gt; zip(keys, map(len, keys))
&amp;lt;zip object at 0x7fc16860d440&amp;gt;
&amp;gt;&amp;gt;&amp;gt; list(zip(keys, map(len, keys)))
[(&apos;foobar&apos;, 6), (&apos;barzz&apos;, 5), (&apos;ba!&apos;, 3)]
&amp;gt;&amp;gt;&amp;gt; dict(zip(keys, map(len, keys)))
{&apos;foobar&apos;: 6, &apos;barzz&apos;: 5, &apos;ba!&apos;: 3}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;What&apos;s Next?&lt;/h2&gt;
&lt;p&gt;While Python is often advertised as being object oriented, it can be used in a very functional manner. A lot of its built-in concepts, such as generators and list comprehension, are functionally oriented and don’t conflict with an object-oriented approach. Python provides a large set of builtin functions that can help you keeping your code with no side effects. That also limits the reliance on a program’s global state, for your own good.&lt;/p&gt;
&lt;p&gt;In the next blog post, we&apos;ll see how you can leverage Python &lt;em&gt;functools&lt;/em&gt; and &lt;em&gt;itertools&lt;/em&gt; module to enhance your functional adventure. Stay tuned!&lt;/p&gt;
</content:encoded></item><item><title>Writing Your Own Filtering DSL in Python</title><link>https://julien.danjou.info/blog/writing-your-own-filtering-dsl-in-python/</link><guid isPermaLink="true">https://julien.danjou.info/blog/writing-your-own-filtering-dsl-in-python/</guid><description>A few months ago, we&apos;ve seen how to write a filtering syntax tree in Python. The idea behind this was to create a data structure — in the form of a dictionary — that would allow to filter data based.</description><pubDate>Mon, 01 Apr 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few months ago, &lt;a href=&quot;https://julien.danjou.info/blog/multi-value-syntax-tree-filtering-in-python&quot;&gt;we&apos;ve seen how to write a filtering syntax tree&lt;/a&gt; in Python. The idea behind this was to create a data structure — in the form of a dictionary — that would allow to filter data based on conditions.&lt;/p&gt;
&lt;p&gt;Our API looked like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; f = Filter(
  {&quot;and&quot;: [
    {&quot;eq&quot;: (&quot;foo&quot;, 3)},
    {&quot;gt&quot;: (&quot;bar&quot;, 4)},
   ]
  },
)
&amp;gt;&amp;gt;&amp;gt; f(foo=3, bar=5)
True
&amp;gt;&amp;gt;&amp;gt; f(foo=4, bar=5)
False
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While such a mechanism is pretty powerful to use, the input data structure format might not be user friendly. It&apos;s great to use, for example, with a JSON based REST API, but it&apos;s pretty terrible to use for a command-line interface.&lt;/p&gt;
&lt;p&gt;A good solution to that problem is to build our own &lt;em&gt;language&lt;/em&gt;. That&apos;s called a DSL.&lt;/p&gt;
&lt;h2&gt;Building a DSL&lt;/h2&gt;
&lt;p&gt;What&apos;s a Domain-Specific Language (DSL)? It&apos;s a computer language that is specialized to a certain domain. In our case, our domain is filtering, as we&apos;re providing a &lt;em&gt;Filter&lt;/em&gt; class that allows to filter a set of value.&lt;/p&gt;
&lt;p&gt;How do you build a data structure such as &lt;code&gt;{&quot;and&quot;: [{&quot;eq&quot;: (&quot;foo&quot;, 3)}, {&quot;gt&quot;: (&quot;bar&quot;, 4)}]}&lt;/code&gt; from a string? Well, you define a language, parse it, and then convert it to the right format.&lt;/p&gt;
&lt;p&gt;In order to parse a language, there are a lot of different solutions, from implementing manual parsers to using regular expression. In this case, we&apos;ll use &lt;a href=&quot;https://en.wikipedia.org/wiki/Lexical_analysis&quot;&gt;lexical analsysis&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;First Iteration&lt;/h3&gt;
&lt;p&gt;Let&apos;s start small and define the base of our grammar. That should be something simple, so we&apos;ll go with &lt;code&gt;&amp;lt;identifier&amp;gt;&amp;lt;operator&amp;gt;&amp;lt;value&amp;gt;&lt;/code&gt;. For example &lt;code&gt;&quot;foobar&quot;=&quot;baz&quot;&lt;/code&gt; is a valid sentence in our grammar and will conver to &lt;code&gt;{&quot;=&quot;: (&quot;foobar&quot;, &quot;baz&quot;)}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The following code snippet leverages &lt;a href=&quot;https://pypi.org/project/pyparsing/&quot;&gt;&lt;em&gt;pyparsing&lt;/em&gt;&lt;/a&gt; for parsing the string and specifying the grammar:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import pyparsing

identifier = pyparsing.QuotedString(&apos;&quot;&apos;)
operator = (
    pyparsing.Literal(&quot;=&quot;) |
    pyparsing.Literal(&quot;≠&quot;) |
    pyparsing.Literal(&quot;≥&quot;) |
    pyparsing.Literal(&quot;≤&quot;) |
    pyparsing.Literal(&quot;&amp;lt;&quot;) |
    pyparsing.Literal(&quot;&amp;gt;&quot;)
)
value = pyparsing.QuotedString(&apos;&quot;&apos;)

match_format = identifier + operator + value

print(match_format.parseString(&apos;&quot;foobar&quot;=&quot;123&quot;&apos;))

## Prints:
## [&apos;foobar&apos;, &apos;=&apos;, &apos;123&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that simple grammar, we can parse and get a token list composed of our 3 items: the identifier, the operator and the value.&lt;/p&gt;
&lt;h3&gt;Transforming the Data&lt;/h3&gt;
&lt;p&gt;The list above in the format &lt;code&gt;[identifier, operator, value]&lt;/code&gt; is not really what we need in the end. We need something like &lt;code&gt;{operator: (identifier, value)}&lt;/code&gt;. We can leverage &lt;em&gt;pyparsing&lt;/em&gt; API to help us with that.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def list_to_dict(pos, tokens):
    return {tokens[1]: (tokens[0], tokens[2])}

match_format = (identifier + operator + value).setParseAction(list_to_dict)

print(match_format.parseString(&apos;&quot;foobar&quot;=&quot;123&quot;&apos;))

## Prints:
## [{&apos;=&apos;: (&apos;foobar&apos;, &apos;123&apos;)}]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;parseString&lt;/code&gt; method allows to modify the returned value of a grammar token. In that case, we transform the list of the dict we need.&lt;/p&gt;
&lt;h3&gt;Plugging the Parser and the Filter&lt;/h3&gt;
&lt;p&gt;In the following code, we&apos;ll reuse the &lt;code&gt;Filter&lt;/code&gt; class we wrote in &lt;a href=&quot;https://julien.danjou.info/blog/multi-value-syntax-tree-filtering-in-python&quot;&gt;our previous post&lt;/a&gt;. We&apos;ll just add the following code to our previous example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def parse_string(s):
    return match_format.parseString(s, parseAll=True)[0]

f = Filter(parse_string(&apos;&quot;foobar&quot;=&quot;baz&quot;&apos;))
print(f(foobar=&quot;baz&quot;))
print(f(foobar=&quot;biz&quot;))

## Prints:
## True
## False
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, we have a pretty simple parser and a good way to build a &lt;code&gt;Filter&lt;/code&gt; object from a string.&lt;/p&gt;
&lt;p&gt;As our &lt;em&gt;Filter&lt;/em&gt; object supports complex and nested operations, such as &lt;code&gt;and&lt;/code&gt; and &lt;code&gt;or&lt;/code&gt;, we could also add it to the grammar — I&apos;ll leave that to you reader as an exercise!&lt;/p&gt;
&lt;h3&gt;Building your own Grammar&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;pyparsing&lt;/em&gt; makes it easy to build one&apos;s own grammar. However, it should not be abused: building a DSL means that your users will have to discover and learn it. If it&apos;s way different that what they know and already exists, it might be cumbersome for them.&lt;/p&gt;
&lt;p&gt;Finally, if you&apos;re curious and want to see a real world usage, &lt;a href=&quot;https://doc.mergify.io/conditions.html#grammar&quot;&gt;Mergify condition system&lt;/a&gt; leverages &lt;em&gt;pyparsing&lt;/em&gt; to &lt;a href=&quot;https://github.com/Mergifyio/mergify-engine/blob/master/mergify_engine/rules/parser.py&quot;&gt;implement its parser&lt;/a&gt;. Check it out!&lt;/p&gt;
</content:encoded></item><item><title>Python + Memcached: Efficient Caching in Distributed Applications</title><link>https://julien.danjou.info/blog/python-memcached-efficient-caching-in-distributed-applications/</link><guid isPermaLink="true">https://julien.danjou.info/blog/python-memcached-efficient-caching-in-distributed-applications/</guid><description>When writing Python applications, caching is important. Using a cache to avoid recomputing data or accessing a slow database can provide you with a great performance boost.  Python offers built-in pos</description><pubDate>Mon, 04 Mar 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When writing Python applications, caching is important. Using a cache to avoid recomputing data or accessing a slow database can provide you with a great performance boost.&lt;/p&gt;
&lt;p&gt;Python offers built-in possibilities for caching, from a simple dictionary to a more complete data structure such as &lt;a href=&quot;https://docs.python.org/3/library/functools.html#functools.lru_cache&quot;&gt;&lt;code&gt;functools.lru_cache&lt;/code&gt;&lt;/a&gt;. The latter can cache any item using a &lt;a href=&quot;https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_Recently_Used_(LRU)&quot;&gt;Least-Recently Used algorithm&lt;/a&gt; to limit the cache size.&lt;/p&gt;
&lt;p&gt;Those data structures are, however, by definition &lt;em&gt;local&lt;/em&gt; to your Python process. When several copies of your application run across a large platform, using a in-memory data structure disallows sharing the cached content. This can be a problem for large-scale and distributed applications.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://files.realpython.com/media/python-memcached.97e1deb2aa17.png&quot; alt=&quot;Python + Memcached System Design Diagram&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Therefore, when a system is distributed across a network, it also needs a cache that is running on the network. Nowadays, there are plenty of network servers that offer caching capability—for example, &lt;a href=&quot;https://redis.io&quot;&gt;Redis&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As you’re going to see in this tutorial, &lt;a href=&quot;http://memcached.org/&quot;&gt;memcached&lt;/a&gt; is another great option for caching. After a quick introduction to basic memcached usage, you’ll learn about advanced patterns such as “cache and set” and using fallback caches to avoid cold cache performance issues.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;Installing memcached&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Memcached&lt;/em&gt; is &lt;a href=&quot;https://github.com/memcached/memcached/wiki/Install&quot;&gt;available for many platforms&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you run &lt;strong&gt;Linux&lt;/strong&gt;, you can install it using &lt;code&gt;apt-get install memcached&lt;/code&gt; or &lt;code&gt;yum install memcached&lt;/code&gt;. This will install memcached from a pre-built package but you can alse build memcached from source, &lt;a href=&quot;https://github.com/memcached/memcached/wiki/Install&quot;&gt;as explained here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;macOS&lt;/strong&gt;, using &lt;a href=&quot;https://brew.sh/&quot;&gt;Homebrew&lt;/a&gt; is the simplest option. Just run &lt;code&gt;brew install memcached&lt;/code&gt; after you’ve installed the Homebrew package manager.&lt;/li&gt;
&lt;li&gt;On &lt;strong&gt;Windows&lt;/strong&gt;, you would have to compile memcached yourself or find &lt;a href=&quot;https://commaster.net/content/installing-memcached-windows&quot;&gt;pre-compiled binaries&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once installed, &lt;em&gt;memcached&lt;/em&gt; can simply be launched by calling the &lt;code&gt;memcached&lt;/code&gt; command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ memcached
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before you can interact with memcached from Python-land you’ll need to install a memcached &lt;em&gt;client&lt;/em&gt; library. You’ll see how to do this in the next section, along with some basic cache access operations.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;Storing and Retrieving Cached Values Using Python&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;If you never used &lt;em&gt;memcached&lt;/em&gt;, it is pretty easy to understand. It basically provides a giant network-available dictionary. This dictionary has a few properties that are different from a classical Python dictionnary, mainly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keys and values have to be bytes&lt;/li&gt;
&lt;li&gt;Keys and values are automatically deleted after an expiration time&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Therefore, the two basic operations for interacting with &lt;em&gt;memcached&lt;/em&gt; are &lt;code&gt;set&lt;/code&gt; and &lt;code&gt;get&lt;/code&gt;. As you might have guessed, they’re used to assign a value to a key or to get a value from a key, respectively.&lt;/p&gt;
&lt;p&gt;My preferred Python library for interacting with &lt;em&gt;memcached&lt;/em&gt; is &lt;a href=&quot;https://pypi.python.org/pypi/pymemcache&quot;&gt;&lt;code&gt;pymemcache&lt;/code&gt;&lt;/a&gt;—I recommend using it. You can simply &lt;a href=&quot;https://realpython.com/learn/python-first-steps/#11-pythons-power-packagesmodules&quot;&gt;install it using pip&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;$ pip install pymemcache&lt;/p&gt;
&lt;p&gt;The following code shows how you can connect to &lt;em&gt;memcached&lt;/em&gt; and use it as a network cache in your Python applications:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; from pymemcache.client import base
## Don&apos;t forget to run `memcached&apos; before running this next line:
&amp;gt;&amp;gt;&amp;gt; client = base.Client((&apos;localhost&apos;, 11211))
## Once the client is instantiated, you can access the cache:
&amp;gt;&amp;gt;&amp;gt; client.set(&apos;some_key&apos;, &apos;some value&apos;)
## Retrieve previously set data again:
&amp;gt;&amp;gt;&amp;gt; client.get(&apos;some_key&apos;)&apos;some value&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;memcached&lt;/em&gt; network protocol is really simple an its implementation extremely fast, which makes it useful to store data that would be otherwise slow to retrieve from the canonical source of data or to compute again.&lt;/p&gt;
&lt;p&gt;While straightforward enough, this example allows storing key/value tuples across the network and accessing them through multiple, distributed, running copies of your application. This is simplistic, yet powerful. And it’s a great first step towards optimizing your application.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;Automatically Expiring Cached Data&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;When storing data into &lt;em&gt;memcached&lt;/em&gt;, you can set an expiration time—a maximum number of seconds for &lt;em&gt;memcached&lt;/em&gt; to keep the key and value around. After that delay, &lt;em&gt;memcached&lt;/em&gt; automatically removes the key from its cache.&lt;/p&gt;
&lt;p&gt;What should you set this cache time to? There is no magic number for this delay, and it will entirely depend on the type of data and application that you are working with. It could be a few seconds, or it might be a few hours.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Cache invalidation&lt;/em&gt;, which defines when to remove the cache because it is out of sync with the current data, is also something that your application will have to handle. Especially if presenting data that is too old or &lt;em&gt;stale&lt;/em&gt; is to be avoided.&lt;/p&gt;
&lt;p&gt;Here again, there is no magical recipe; it depends on the type of application you are building. However, there are several outlying cases that should be handled—which we haven’t yet covered in the above example.&lt;/p&gt;
&lt;p&gt;A caching server cannot grow infinitely—memory is a finite resource. Therefore, keys will be flushed out by the caching server as soon as it needs more space to store other things.&lt;/p&gt;
&lt;p&gt;Some keys might also be expired because they reached their expiration time (also sometimes called the “time-to-live” or TTL.) In those cases the data is lost, and the canonical data source must be queried again.&lt;/p&gt;
&lt;p&gt;This sounds more complicated than it really is. You can generally work with the following pattern when working with &lt;em&gt;memcached&lt;/em&gt; in Python:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pymemcache.client import base
def do_some_query():
    # Replace with actual querying code to a database,
    # a remote REST API, etc.
    return 42
    
## Don&apos;t forget to run `memcached&apos; before running this code
client = base.Client((&apos;localhost&apos;, 11211))
result = client.get(&apos;some_key&apos;)
if result is None:
    # The cache is empty, need to get the value
        # from the canonical source:
        result = do_some_query()
        # Cache the result for next time:
        client.set(&apos;some_key&apos;, result)
        # Whether we needed to update the cache or not,
        # at this point you can work with the data
        # stored in the `result` variable:
        print(result)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Handling missing keys is mandatory because of normal flush-out operations. It is also obligatory to handle the cold cache scenario, i.e. when &lt;em&gt;memcached&lt;/em&gt; has just been started. In that case, the cache will be entirely empty and the cache needs to be fully repopulated, one request at a time.&lt;/p&gt;
&lt;p&gt;This means you should view any cached data as ephemeral. And you should never expect the cache to contain a value you previously wrote to it.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;Warming Up a Cold Cache&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Some of the cold cache scenarios cannot be prevented, for example a &lt;em&gt;memcached&lt;/em&gt; crash. But some can, for example migrating to a new &lt;em&gt;memcached&lt;/em&gt; server.&lt;/p&gt;
&lt;p&gt;When it is possible to predict that a cold cache scenario will happen, it is better to avoid it. A cache that needs to be refilled means that all of the sudden, the canonical storage of the cached data will be massively hit by all cache users who lack a cache data (also known as the &lt;a href=&quot;https://en.wikipedia.org/wiki/Thundering_herd_problem&quot;&gt;thundering herd problem&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;&lt;em&gt;pymemcache&lt;/em&gt; provides a class named &lt;code&gt;FallbackClient&lt;/code&gt; that helps in implementing this scenario as demonstrated here:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pymemcache.client import base
from pymemcache import fallback

def do_some_query():
    # Replace with actual querying code to a database,
    # a remote REST API, etc.
    return 42
    
## Set `ignore_exc=True` so it is possible to shut down
## the old cache before removing its usage from
## the program, if ever necessary.
old_cache = base.Client((&apos;localhost&apos;, 11211), ignore_exc=True)
new_cache = base.Client((&apos;localhost&apos;, 11212))

client = fallback.FallbackClient((new_cache, old_cache))

result = client.get(&apos;some_key&apos;)

if result is None:
    # The cache is empty, need to get the value
    # from the canonical source:
    result = do_some_query()
    # Cache the result for next time:
    client.set(&apos;some_key&apos;, result)
    print(result)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;FallbackClient&lt;/code&gt; queries the old cache passed to its constructor, respecting the order. In this case, the new cache server will always be queried first, and in case of a cache miss, the old one will be queried—avoiding a possible return-trip to the primary source of data.&lt;/p&gt;
&lt;p&gt;If any key is set, it will only be set to the new cache. After some time, the old cache can be decommissioned and the &lt;code&gt;FallbackClient&lt;/code&gt; can be replaced directed with the &lt;code&gt;new_cache&lt;/code&gt;client.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;Check And Set&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;When communicating with a remote cache, the usual concurrency problem comes back: there might be several clients trying to access the same key at the same time. &lt;em&gt;memcached&lt;/em&gt; provides a &lt;em&gt;check and set&lt;/em&gt; operation, shortened to &lt;em&gt;CAS&lt;/em&gt;, which helps to solve this problem.&lt;/p&gt;
&lt;p&gt;The simplest example is an application that wants to count the number of users it has. Each time a visitor connects, a counter is incremented by 1. Using &lt;em&gt;memcached&lt;/em&gt;, a simple implementation would be:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def on_visit(client):
    result = client.get(&apos;visitors&apos;)
    if result is None:
        result = 1
    else:
        result += 1
    client.set(&apos;visitors&apos;, result)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, what happens if two instances of the application try to update this counter at the same time?&lt;/p&gt;
&lt;p&gt;The first call &lt;code&gt;client.get(&apos;visitors&apos;)&lt;/code&gt; will return the same number of visitors for both of them, let’s say it’s 42. Then both will add 1, compute 43, and set the number of visitors to 43. That number is wrong, and the result should be 44, i.e. 42 + 1 + 1.&lt;/p&gt;
&lt;p&gt;To solve this concurrency issue, the CAS operation of &lt;em&gt;memcached&lt;/em&gt; is handy. The following snippet implements a correct solution:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def on_visit(client):
    while True:
        result, cas = client.gets(&apos;visitors&apos;)
        if result is None:
            result = 1
        else:
            result += 1
        if client.cas(&apos;visitors&apos;, result, cas):
             break
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;gets&lt;/code&gt; method returns the value, just like the &lt;code&gt;get&lt;/code&gt; method, but it also returns a &lt;em&gt;CAS value&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;What is in this value is not relevant, but it is used for the next method &lt;code&gt;cas&lt;/code&gt; call. This method is equivalent to the &lt;code&gt;set&lt;/code&gt; operation, except that it fails if the value has changed since the &lt;code&gt;gets&lt;/code&gt; operation. In case of success, the loop is broken. Otherwise, the operation is restarted from the beginning.&lt;/p&gt;
&lt;p&gt;In the scenario where two instances of the application try to update the counter at the same time, only one succeeds to move the counter from 42 to 43. The second instance gets a &lt;code&gt;False&lt;/code&gt; value returned by the &lt;code&gt;client.cas&lt;/code&gt; call, and have to retry the loop. It will retrieve 43 as value this time, will increment it to 44, and its &lt;code&gt;cas&lt;/code&gt; call will succeed, thus solving our problem.&lt;/p&gt;
&lt;p&gt;Incrementing a counter is interesting as an example to explain how CAS works because it is simplistic. However, &lt;em&gt;memcached&lt;/em&gt; also provides the &lt;code&gt;incr&lt;/code&gt; and &lt;code&gt;decr&lt;/code&gt; methods to increment or decrement an integer in a single request, rather than doing multiple &lt;code&gt;gets&lt;/code&gt;/&lt;code&gt;cas&lt;/code&gt; calls. In real-world applications &lt;code&gt;gets&lt;/code&gt; and &lt;code&gt;cas&lt;/code&gt; are used for more complex data type or operations&lt;/p&gt;
&lt;p&gt;Most remote caching server and data store provide such a mechanism to prevent concurrency issues. It is critical to be aware of those cases to make proper use of their features.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;Beyond Caching&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;The simple techniques illustrated in this article showed you how easy it is to leverage &lt;em&gt;memcached&lt;/em&gt; to speed up the performances of your Python application.&lt;/p&gt;
&lt;p&gt;Just by using the two basic “set” and “get” operations you can often accelerate data retrieval or avoid recomputing results over and over again. With &lt;em&gt;memcached&lt;/em&gt; you can share the cache accross a large number of distributed nodes.&lt;/p&gt;
&lt;p&gt;Other, more advanced patterns you saw in this tutorial, like the _Check And Set (CAS)_operation allow you to update data stored in the cache concurrently across multiple Python threads or processes while avoiding data corruption.&lt;/p&gt;
&lt;p&gt;If you are interested into learning more about advanced techniques to write faster and more scalable Python applications, check out &lt;a href=&quot;https://scaling-python.com/&quot;&gt;Scaling Python&lt;/a&gt;. It covers many advanced topics such as network distribution, queuing systems, distributed hashing, and code profiling.&lt;/p&gt;
</content:encoded></item><item><title>How to Log Properly in Python</title><link>https://julien.danjou.info/blog/how-to-log-properly-in-python/</link><guid isPermaLink="true">https://julien.danjou.info/blog/how-to-log-properly-in-python/</guid><description>Logging is one of the most underrated features. Often ignored by software engineers, it can save your time when your application&apos;s running in production.  Most teams don&apos;t think about it until it&apos;s to</description><pubDate>Mon, 04 Feb 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Logging is one of the most underrated features. Often ignored by software engineers, it can save your time when your application&apos;s running in production.&lt;/p&gt;
&lt;p&gt;Most teams don&apos;t think about it until it&apos;s too late in their development process. It&apos;s when things start to get wrong in deployments that somebody realizes too late that logging is missing.&lt;/p&gt;
&lt;h2&gt;Guidelines&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://12factor.net&quot;&gt;Twelve-Factor App&lt;/a&gt; &lt;a href=&quot;https://12factor.net/logs&quot;&gt;defines logs&lt;/a&gt; as a &lt;em&gt;stream of aggregated, time-ordered events collected from the output streams of all running processes&lt;/em&gt;. It also describes how applications should handle their logging. We can summarize those guidelines as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Logs have no fixed beginning or end.&lt;/li&gt;
&lt;li&gt;Print logs to &lt;code&gt;stdout&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Print logs unbuffered.&lt;/li&gt;
&lt;li&gt;The environment is responsible for capturing the stream.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;From my experience, this set of rules is a good trade-off. Logs have to be kept pretty simple to be efficient and reliable. Building complex logging systems might make it harder to get insight into a running application.&lt;/p&gt;
&lt;p&gt;There&apos;s also no point in duplication effort in log management (e.g., log file rotation, archival policy, etc) in your different applications. Having an external workflow that can be shared across different programs seems more efficient.&lt;/p&gt;
&lt;h2&gt;In Python&lt;/h2&gt;
&lt;p&gt;Python provides a logging subsystem with its &lt;a href=&quot;https://docs.python.org/3/library/logging.html&quot;&gt;&lt;em&gt;logging&lt;/em&gt;&lt;/a&gt; module. This module provides a &lt;em&gt;Logger&lt;/em&gt; object that allows you to emit messages with different levels of criticality. Those messages can then be filtered and send to different handlers.&lt;/p&gt;
&lt;p&gt;Let&apos;s have an example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import logging

logger = logging.getLogger(&quot;myapp&quot;)
logger.error(&quot;something wrong&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Depending on the version of Python you&apos;re running you&apos;ll either see:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;No handlers could be found for logger &quot;test123&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;something wrong
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Python 2 used to have no logging setup by default, so it would print an error message about no handler being found. Since Python 3, a default handler outputting to &lt;code&gt;stdout&lt;/code&gt; is now installed — matching the requirements from the 12factor App.&lt;/p&gt;
&lt;p&gt;However, this default setup is far from being perfect.&lt;/p&gt;
&lt;h2&gt;Shortcomings&lt;/h2&gt;
&lt;p&gt;The default format that Python uses does not embed any contextual information. There is no way to know the name of the logger — &lt;code&gt;myapp&lt;/code&gt; in the previous example — nor the date and time of the logged message.&lt;/p&gt;
&lt;p&gt;You &lt;strong&gt;must&lt;/strong&gt; configure Python logging subsystem to enhance its output format.&lt;/p&gt;
&lt;p&gt;To do that, I advise using the &lt;em&gt;&lt;a href=&quot;https://github.com/jd/daiquiri&quot;&gt;daiquiri&lt;/a&gt;&lt;/em&gt; module. It provides an excellent default configuration and a simple API to configure logging, plus some exciting features.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/12/markus-spiske-109588-unsplash.jpg&quot; alt=&quot;Close-up of code on a screen illustrating logging configuration&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Logging Setup&lt;/h2&gt;
&lt;p&gt;When using &lt;em&gt;daiquiri&lt;/em&gt;, the first thing to do is to set up your logging correctly. This can be done with the &lt;code&gt;daiquiri.setup&lt;/code&gt; function as this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import daiquiri

daiquiri.setup()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As simple as that. You can tweak the setup further by asking it to log to file, to change the default string formats, etc, but just calling &lt;code&gt;daiquiri.setup&lt;/code&gt; is enough to get a proper logging default.&lt;/p&gt;
&lt;p&gt;See:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import daiquiri

daiquiri.setup()
daiquiri.getLogger(&quot;myapp&quot;).error(&quot;something wrong&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;outputs:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2018-12-13 10:24:04,373 [38550] ERROR    myapp: something wrong
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If your terminal supports writing text in colors, the line will be printed in red since it&apos;s an error. The format provided by &lt;em&gt;daiquiri&lt;/em&gt; is better than Python&apos;s default: this one includes a timestamp, the process ID,  the criticality level and the logger&apos;s name. Needless to say that this format can also be customized.&lt;/p&gt;
&lt;h2&gt;Passing Contextual Information&lt;/h2&gt;
&lt;p&gt;Logging strings are boring. Most of the time, engineers end up writing code such as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;logger.error(&quot;Something wrong happened with %s when writing data at %d&quot;, myobject.myfield, myobject.mynumber&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The issue with this approach is that you have to think about each field that you want to log about your object, and to make sure that they are inserted correctly in your sentence. If you forget an essential field to describe your object and the problem, you&apos;re screwed.&lt;/p&gt;
&lt;p&gt;A reliable alternative to this manual crafting of log strings is to pass interesting objects as keyword arguments. &lt;em&gt;Daiquiri&lt;/em&gt; supports it, and it works that way:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import attr
import daiquiri
import requests

daiquiri.setup()
logger = daiquiri.getLogger(&quot;myapp&quot;)

@attr.s
class Request:
     url = attr.ib()
     status_code = attr.ib(init=False, default=None)
     
     def get(self):
         r = requests.get(self.url)
         self.status_code = r.status_code
         r.raise_for_status()
         return r

user = &quot;jd&quot;
req = Request(&quot;https://google.com/not-this-page&quot;)
try:
    req.get()
except Exception:
    logger.error(&quot;Something wrong happened during the request&quot;,
                 request=req, user=user)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If anything goes wrong with the request, it will be logged with the stack trace, like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2018-12-14 10:37:24,586 [43644] ERROR    myapp [request: Request(url=&apos;https://google.com/not-this-page&apos;, status_code=404)] [user: jd]: Something wrong happened during the request
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, the call to &lt;code&gt;logger.error&lt;/code&gt; is pretty straight-forward: a line that explains what&apos;s wrong, and then the different interesting objects are passed as keyword arguments.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Daiquiri&lt;/em&gt; logs those keyword arguments with a default format of &lt;code&gt;[key: value]&lt;/code&gt; that is included as a prefix to the log string. The value is printed using its &lt;code&gt;__format__&lt;/code&gt; method — that&apos;s why I&apos;m using the &lt;em&gt;&lt;a href=&quot;http://www.attrs.org/en/stable/&quot;&gt;attr&lt;/a&gt;&lt;/em&gt; module here: it automatically generates this method for me and includes all fields by default. You can also customize &lt;em&gt;daiquiri&lt;/em&gt; to use any other format.&lt;/p&gt;
&lt;p&gt;Following those guidelines should be a perfect start for logging correctly with Python!&lt;/p&gt;
</content:encoded></item><item><title>Serious Python released!</title><link>https://julien.danjou.info/blog/serious-python-released/</link><guid isPermaLink="true">https://julien.danjou.info/blog/serious-python-released/</guid><description>Today I&apos;m glad to announce that my new book, Serious Python, has been released.  However, you wonder… what is Serious Python?  Well, Serious Python is the the new name of The Hacker&apos;s Guide to Python</description><pubDate>Thu, 17 Jan 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Today I&apos;m glad to announce that my new book, Serious Python, has been released.&lt;/p&gt;
&lt;p&gt;However, you wonder… what is &lt;em&gt;Serious Python&lt;/em&gt;?&lt;/p&gt;
&lt;p&gt;Well, Serious Python is the the new name of &lt;em&gt;The Hacker&apos;s Guide to Python&lt;/em&gt; — the first book I published. Serious Python is the 4th update of that book — but with a brand a new name and a new editor!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/01/serious-python.png&quot; alt=&quot;Cover of Serious Python&quot; /&gt;&lt;/p&gt;
&lt;p&gt;For more than a year, I&apos;ve been working with the editor &lt;a href=&quot;https://nostarch.com&quot;&gt;No Starch Press&lt;/a&gt; to enhance this book and bring it to the next level! I&apos;m very proud of what we achieved, and working with a whole team on this book has been a fantastic experience.&lt;/p&gt;
&lt;p&gt;The content has been updated to be ready for 2019: &lt;em&gt;pytest&lt;/em&gt; is now a de-facto standard for testing, so I had to write about it. On the other hand, Python 2 support was less a focus, and I removed many mentions of Python 2 altogether. Some chapters have been reorganized, regrouped and others got enhanced with new content!&lt;/p&gt;
&lt;p&gt;The good news: you can get this new edition of the book with a &lt;strong&gt;15% discount&lt;/strong&gt; for the next 24 hours using the coupon code &lt;strong&gt;SERIOUSPYTHONLAUNCH&lt;/strong&gt; on the &lt;a href=&quot;https://serious-python.com&quot;&gt;book page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The book is also released as part as No Starch collection. They also are in charge of distributing the paperback copy of the book. If you want a version of the book that you can touch and hold in your arms, look for it in &lt;a href=&quot;https://nostarch.com/seriouspython&quot;&gt;No Starch shop&lt;/a&gt;, on &lt;a href=&quot;https://www.amazon.com/gp/product/B074S4G1L5/ref=as_li_tl?ie=UTF8&amp;amp;camp=1789&amp;amp;creative=9325&amp;amp;creativeASIN=B074S4G1L5&amp;amp;linkCode=as2&amp;amp;tag=juliendanjou-20&amp;amp;linkId=2d68dde537d79ba5e334d4291ad37fff&quot;&gt;Amazon&lt;/a&gt; or in your favorite book shop!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/01/hackerspython_cover-front_v5.png&quot; alt=&quot;No Starch version of Serious Python cover&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>Why You Should Care That Your SQL DDL is Transactional</title><link>https://julien.danjou.info/blog/why-you-should-care-that-your-sql-ddl-is-transactional/</link><guid isPermaLink="true">https://julien.danjou.info/blog/why-you-should-care-that-your-sql-ddl-is-transactional/</guid><description>I don&apos;t write a lot about database management. How come? I&apos;m a software engineer, and like many of my peers, I leverage databases to store data. I should talk more about this! What made me write this</description><pubDate>Mon, 07 Jan 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I don&apos;t write a lot about database management. How come? I&apos;m a software engineer, and like many of my peers, I leverage databases to store data. I should talk more about this! What made me write this today is that I&apos;ve discovered that many of my peers wouldn&apos;t be able to understand the title of this post.&lt;/p&gt;
&lt;p&gt;However, I can tell you that once you&apos;ve finished reading this, you&apos;ll thank me!&lt;/p&gt;
&lt;h2&gt;DDL?&lt;/h2&gt;
&lt;p&gt;To understand what this post is about, let&apos;s start with DDL. DDL is the abbreviation of &lt;em&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Data_definition_language&quot;&gt;Data Definition Language&lt;/a&gt;&lt;/em&gt;. In summary, a DDL is a language that allows defining your data structure. A famous one is the SQL DDL — and that&apos;s the one I talk about here.&lt;/p&gt;
&lt;p&gt;I&apos;m sure you already used it if you created a relational database with &lt;code&gt;CREATE TABLE foo (id INTEGER)&lt;/code&gt;. That is a DDL statement.&lt;/p&gt;
&lt;p&gt;In SQL, there&apos;s a lot of DDL operations you can do, such as creating a table, renaming a table, creating or removing a column, converting a column to a new type, etc.&lt;/p&gt;
&lt;p&gt;Those DDL statements are commonly used in two cases:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When creating your database&apos; tables for the first time. You issue a bunch of &lt;code&gt;CREATE TABLE&lt;/code&gt; statements, and your database is ready to be used.&lt;/li&gt;
&lt;li&gt;When updating your database by adding, removing or modifying tables or columns. This is typically done when upgrading your application to a new version.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The fact that our DDL is transactional or not in option 1. has often little impact in practice. It&apos;s can still be useful, where for example you could get an error because the disk is full — having the ability to roll back in this case can be a life saviour.&lt;/p&gt;
&lt;p&gt;In our case here, we&apos;ll talk about why you need a transactional DDL when upgrading your database.&lt;/p&gt;
&lt;h3&gt;Transactional You Said?&lt;/h3&gt;
&lt;p&gt;What transactional means here? It means that we can issue those DDL statements inside a transaction.&lt;/p&gt;
&lt;p&gt;Wait, what&apos;s a transaction? To make it simple, in a database, a transaction is a group of operations that are treated as a single coherent operation, independently of other transactions. The final operation has to be &lt;strong&gt;a&lt;/strong&gt;tomic, &lt;strong&gt;c&lt;/strong&gt;onsistent, &lt;strong&gt;i&lt;/strong&gt;solated and &lt;strong&gt;d&lt;/strong&gt;urable — therefore that &lt;strong&gt;ACID&lt;/strong&gt; property you keep reading about while always wondering what it meant. The operations composing the transaction are either entirely executed, or not at all.&lt;/p&gt;
&lt;p&gt;In our case, having the DDL being transactional means one simple thing: the ability to execute several operations (e.g., several &lt;code&gt;ALTER TABLE&lt;/code&gt;) in a single operation, that can be either committed or rolled back.&lt;/p&gt;
&lt;p&gt;Let&apos;s use an example. Here&apos;s a table &lt;code&gt;ingredients&lt;/code&gt; with a &lt;code&gt;name&lt;/code&gt; column created with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE TABLE ingredients (
  name text NOT NULL
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this table, there is a list of ingredients in the form of &lt;code&gt;water 20 mL&lt;/code&gt;, &lt;code&gt;flour 300 g&lt;/code&gt;, etc.&lt;/p&gt;
&lt;p&gt;Now, we&apos;re upgrading our application, and we want to handle the quantity of ingredients in their columns to make it easier to query the data. Let&apos;s say we&apos;re going to handle quantity and quantity units for our ingredients. We need to add two new columns to our table schema, &lt;code&gt;quantity&lt;/code&gt; and &lt;code&gt;unit&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ALTER TABLE ingredients ADD COLUMN quantity integer NOT NULL;
ALTER TABLE ingredients ADD COLUMN unit text NOT NULL;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also need to convert the &lt;code&gt;name&lt;/code&gt; by splitting it into &lt;code&gt;&amp;lt;name&amp;gt; &amp;lt;quantity&amp;gt; &amp;lt;unit&amp;gt;&lt;/code&gt; and insert this into the new columns. We can do this like that:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UPDATE ingredients SET name=split_part(name, &apos; &apos;, 1), quantity=split_part(name, &apos; &apos;, 2)::int, unit=split_part(name, &apos; &apos;, 3);
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;In this example, I&apos;m using &lt;a href=&quot;https://www.postgresql.org/docs/11/static/functions-string.html&quot;&gt;the &lt;code&gt;split_part&lt;/code&gt; operator from PostgreSQL&lt;/a&gt; to split the string.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;With the &lt;code&gt;UPDATE&lt;/code&gt; statement, the &lt;code&gt;name&lt;/code&gt; column containing &lt;code&gt;flour 300 grams&lt;/code&gt; now contains &lt;code&gt;flour&lt;/code&gt;, and the columns &lt;code&gt;quantity&lt;/code&gt; and &lt;code&gt;unit&lt;/code&gt; respectively stores &lt;code&gt;300&lt;/code&gt; and &lt;code&gt;grams&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When we run our upgrade procedure consisting of those two &lt;code&gt;ALTER TABLE&lt;/code&gt; and one &lt;code&gt;UPDATE&lt;/code&gt;, we got our final table like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## SELECT * FROM ingredients;
 name  │ quantity │ unit
───────┼──────────┼──────
 flour │      300 │ g
(1 row)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Exactly what we want.&lt;/p&gt;
&lt;h2&gt;Ok, So What?&lt;/h2&gt;
&lt;p&gt;In the previous example, everything worked fine. Our 300 grams of flour string is split, converted and stored into the three different columns. However, let&apos;s think about what happens if the conversion fails because our ingredient &lt;code&gt;name&lt;/code&gt; is &lt;code&gt;foobar&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## ALTER TABLE ingredients ADD COLUMN quantity integer;
ALTER TABLE
## ALTER TABLE ingredients ADD COLUMN unit text;
ALTER TABLE
## UPDATE ingredients SET name=split_part(name, &apos; &apos;, 1), quantity=split_part(name, &apos; &apos;, 2)::int, unit=split_part(name, &apos; &apos;, 3);
ERROR:  invalid input syntax for integer: &quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Right, so in this case our update failed because it&apos;s impossible to convert an empty string to an integer.&lt;/p&gt;
&lt;p&gt;We&apos;re going to fix this piece of data in our database (manually or automatically, whatever) to make it work, changing &lt;code&gt;foobar&lt;/code&gt; to something like &lt;code&gt;foobar 1 kg&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then, when we rerun the upgrade script, this is what happens:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## ALTER TABLE ingredients ADD COLUMN quantity integer NOT NULL;
ERROR:  column &quot;quantity&quot; of relation &quot;ingredients&quot; already exists
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The upgrade script failed earlier — not in the &lt;code&gt;UPDATE&lt;/code&gt; statement. It has a good reason to fail: the column &lt;code&gt;quantity&lt;/code&gt; already exists.&lt;/p&gt;
&lt;p&gt;Why is that? Well, when we run the upgrade procedure the first time, we did not run it inside a transaction. Every DDL statement was committed right after its execution. Therefore, the current state of our database is &lt;em&gt;half-migrated&lt;/em&gt;: we have the new schema installed, but not the data migrated.&lt;/p&gt;
&lt;p&gt;This sucks. This should not happen. Ever.&lt;/p&gt;
&lt;h2&gt;Why?&lt;/h2&gt;
&lt;p&gt;Some database systems (e.g., MySQL) do not support DDL running in a transaction, so you have no choice than running the three operations (&lt;code&gt;ALTER&lt;/code&gt;, &lt;code&gt;ALTER&lt;/code&gt; and then &lt;code&gt;UPDATE&lt;/code&gt;) as three distinct operations: if any of those fails, there&apos;s no way to recover and get back to the initial state.&lt;/p&gt;
&lt;p&gt;If you&apos;re using a database that supports running DDL statements inside a transaction (e.g., PostgreSQL), we can run your upgrade script like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;postgres=# BEGIN;
postgres=# ALTER TABLE ingredients ADD COLUMN quantity integer;
ALTER TABLE
postgres=# ALTER TABLE ingredients ADD COLUMN unit text;
ALTER TABLE
postgres=# UPDATE ingredients SET name=split_part(name, &apos; &apos;, 1), quantity=split_part(name, &apos; &apos;, 2)::int, unit=split_part(name, &apos; &apos;, 3);
ERROR:  invalid input syntax for integer: &quot;&quot;
postgres=# ROLLBACK;
ROLLBACK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since the transaction failed, we ended up doing a &lt;code&gt;ROLLBACK&lt;/code&gt;. When checking the state of the database, we can see the state did not change:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## \d ingredients;
           Table &quot;public.ingredients&quot;
 Column │ Type │ Collation │ Nullable │ Default
────────┼──────┼───────────┼──────────┼─────────
 name   │ text │           │ not null │
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Therefore, it&apos;s possible to fix our database content and rerun the migration procedure without being in a &lt;em&gt;half-migrated&lt;/em&gt; state.&lt;/p&gt;
&lt;h2&gt;A Database That Lies&lt;/h2&gt;
&lt;p&gt;When you&apos;re giving data to a database, you&apos;re trusting it. It&apos;d be awful if it were lying to you, right? Check this out:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mysql&amp;gt; CREATE TABLE ingredients (name text NOT NULL);
Query OK, 0 rows affected (0.03 sec)

mysql&amp;gt; BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql&amp;gt; ALTER TABLE ingredients ADD COLUMN quantity integer;
Query OK, 0 rows affected (0.05 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql&amp;gt; ALTER TABLE ingredients ADD COLUMN unit text;
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql&amp;gt; ROLLBACK;
Query OK, 0 rows affected (0.00 sec)

mysql&amp;gt; DESC ingredients;
+----------+---------+------+-----+---------+-------+
| Field    | Type    | Null | Key | Default | Extra |
+----------+---------+------+-----+---------+-------+
| name     | text    | NO   |     | NULL    |       |
| quantity | int(11) | YES  |     | NULL    |       |
| unit     | text    | YES  |     | NULL    |       |
+----------+---------+------+-----+---------+-------+
3 rows in set (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the output above, you can see that we issued two DDL statements inside a transaction and that we then rolled back that transaction. MySQL did not output any error at any time, making us think that it did not alter our table. However, when checking the schema of the database, we can see that nothing has been rolled back. Not only MySQL does not support transactional DDL, but it also fails you entirely and lie about what it&apos;s doing.&lt;/p&gt;
&lt;h2&gt;How Important is That?&lt;/h2&gt;
&lt;p&gt;Transactional DDL is a feature that is often ignored by software engineers, whereas it&apos;s a key feature for managing your database life cycle.&lt;/p&gt;
&lt;p&gt;I&apos;m writing this post today because I&apos;ve been hit by this multiple times over the last years. OpenStack made the choice years ago to go with MySQL, and in consequences, every database upgrade script that fails in the middle of the procedure leave the database is an inconsistence state. In that case, it means that either:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The operator have to determine where the upgrade script stopped, roll back the upgrade by itself, fix the failure, and rerun the upgrade procedure.&lt;/li&gt;
&lt;li&gt;The developer must anticipate every case of potential upgrade failure, write a roll back procedure for each of this case by and test every one of those cases.&lt;/li&gt;
&lt;li&gt;Use a database system that handles transactional DDL.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;No need to tell you that option 3. is the best, option 2. is barely possible to implement and option 1. is what reality looks like. In &lt;a href=&quot;https://gnocchi.xyz&quot;&gt;Gnocchi&lt;/a&gt;, we picked option 3. by recommending operators to use PostgreSQL.&lt;/p&gt;
&lt;p&gt;Next time you use a database, think carefully about what your upgrade procedure will look like!&lt;/p&gt;
</content:encoded></item><item><title>Podcast.__init__: Gnocchi, a Time Series Database for your Metrics</title><link>https://julien.danjou.info/blog/podcast-init-gnocchi/</link><guid isPermaLink="true">https://julien.danjou.info/blog/podcast-init-gnocchi/</guid><description>A few weeks ago, Tobias Macey contacted me as he wanted to talk about Gnocchi, the time series database I&apos;ve been working on for the last few years.  It was a great opportunity to talk about the proje</description><pubDate>Tue, 11 Dec 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few weeks ago, Tobias Macey contacted me as he wanted to talk about Gnocchi, the time series database I&apos;ve been working on for the last few years.&lt;/p&gt;
&lt;p&gt;It was a great opportunity to talk about the project, so I jumped on it! We talk about how Gnocchi came to life, how we built its architecture, the challenges we met, what kind of trade-off we made, etc.&lt;/p&gt;
&lt;p&gt;You can list to this episode &lt;a href=&quot;https://www.podcastinit.com/gnocchi-with-julien-danjou-episode-189/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>A multi-value syntax tree filtering in Python</title><link>https://julien.danjou.info/blog/multi-value-syntax-tree-filtering-in-python/</link><guid isPermaLink="true">https://julien.danjou.info/blog/multi-value-syntax-tree-filtering-in-python/</guid><description>A while ago, we&apos;ve seen how to write a simple filtering syntax tree with Python. The idea was to provide a small abstract syntax tree with an easy to write data structure that would be able to filter.</description><pubDate>Mon, 03 Dec 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A while ago, we&apos;ve seen &lt;a href=&quot;https://julien.danjou.info/blog/simple-filtering-syntax-tree-in-python&quot;&gt;how to write a simple filtering syntax tree with Python&lt;/a&gt;. The idea was to provide a small abstract syntax tree with an easy to write data structure that would be able to filter a value. Filtering meaning that once evaluated, our AST would return either &lt;code&gt;True&lt;/code&gt; or &lt;code&gt;False&lt;/code&gt; based on the passed value.&lt;/p&gt;
&lt;p&gt;With that, we were able to write small rules like &lt;code&gt;Filter({&quot;eq&quot;: 3})(4)&lt;/code&gt; that would return &lt;code&gt;False&lt;/code&gt; since, well, 4 is not equal to 3.&lt;/p&gt;
&lt;p&gt;In this new post, I propose we enhance our filtering ability to support multiple values. The idea is to be able to write something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; f = Filter(
  {&quot;and&quot;: [
    {&quot;eq&quot;: (&quot;foo&quot;, 3)},
    {&quot;gt&quot;: (&quot;bar&quot;, 4)},
   ]
  },
)
&amp;gt;&amp;gt;&amp;gt; f(foo=3, bar=5)
True
&amp;gt;&amp;gt;&amp;gt; f(foo=4, bar=5)
False
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The biggest change here is that the binary operators (&lt;code&gt;eq&lt;/code&gt;, &lt;code&gt;gt&lt;/code&gt;, &lt;code&gt;le&lt;/code&gt;, etc.) now support getting two values, and not only one, and that we can pass multiple values to our filter by using keyword arguments.&lt;/p&gt;
&lt;p&gt;How should we implement that? Well, we can keep the same data structure we built previously. However, this time we&apos;re gonna do the following change:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The left value of the binary operator will be a string that will be used as the key to access the keyword arguments passed to our &lt;code&gt;Filter.__call__&lt;/code&gt; values.&lt;/li&gt;
&lt;li&gt;The right value of the binary operator will be kept as it is (like before).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We therefore need to change our &lt;code&gt;Filter.build_evaluator&lt;/code&gt; to accommodate this as follow:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def build_evaluator(self, tree):
    try:
        operator, nodes = list(tree.items())[0]
    except Exception:
        raise InvalidQuery(&quot;Unable to parse tree %s&quot; % tree)
    try:
        op = self.multiple_operators[operator]
    except KeyError:
        try:
            op = self.binary_operators[operator]
        except KeyError:
            raise InvalidQuery(&quot;Unknown operator %s&quot; % operator)
        assert len(nodes) == 2 # binary operators take 2 values
        def _op(values):
            return op(values[nodes[0]], nodes[1])
        return _op
    # Iterate over every item in the list of the value linked
    # to the logical operator, and compile it down to its own
    # evaluator.
    elements = [self.build_evaluator(node) for node in nodes]
    return lambda values: op((e(values) for e in elements))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The algorithm is pretty much the same, the tree being browsed recursively.&lt;/p&gt;
&lt;p&gt;First, the operator and its arguments (nodes) are extracted.&lt;/p&gt;
&lt;p&gt;Then, if the operator takes multiple arguments (such as &lt;code&gt;and&lt;/code&gt; and &lt;code&gt;or&lt;/code&gt; operators), each node is recursively evaluated and a function is returned evaluating those nodes.&lt;br /&gt;
If the operator is a binary operator (such as &lt;code&gt;eq&lt;/code&gt;, &lt;code&gt;lt&lt;/code&gt;, etc.), it checks that the passed argument list length is 2. Then, it returns a function that will apply the operator (e.g., &lt;code&gt;operator.eq&lt;/code&gt;) to &lt;code&gt;values[nodes[0]]&lt;/code&gt; and &lt;code&gt;nodes[1]&lt;/code&gt;: the former access the arguments (&lt;code&gt;values&lt;/code&gt;) passed to the filter&apos;s &lt;code&gt;__call__&lt;/code&gt; function while the latter is directly the passed argument.&lt;/p&gt;
&lt;p&gt;The full class looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import operator

class InvalidQuery(Exception):
    pass

class Filter(object):
    binary_operators = {
        u&quot;=&quot;: operator.eq,
        u&quot;==&quot;: operator.eq,
        u&quot;eq&quot;: operator.eq,

        u&quot;&amp;lt;&quot;: operator.lt,
        u&quot;lt&quot;: operator.lt,

        u&quot;&amp;gt;&quot;: operator.gt,
        u&quot;gt&quot;: operator.gt,

        u&quot;&amp;lt;=&quot;: operator.le,
        u&quot;≤&quot;: operator.le,
        u&quot;le&quot;: operator.le,

        u&quot;&amp;gt;=&quot;: operator.ge,
        u&quot;≥&quot;: operator.ge,
        u&quot;ge&quot;: operator.ge,

        u&quot;!=&quot;: operator.ne,
        u&quot;≠&quot;: operator.ne,
        u&quot;ne&quot;: operator.ne,
    }

    multiple_operators = {
        u&quot;or&quot;: any,
        u&quot;∨&quot;: any,
        u&quot;and&quot;: all,
        u&quot;∧&quot;: all,
    }

    def __init__(self, tree):
        self._eval = self.build_evaluator(tree)

    def __call__(self, **kwargs):
        return self._eval(kwargs)

    def build_evaluator(self, tree):
        try:
            operator, nodes = list(tree.items())[0]
        except Exception:
            raise InvalidQuery(&quot;Unable to parse tree %s&quot; % tree)
        try:
            op = self.multiple_operators[operator]
        except KeyError:
            try:
                op = self.binary_operators[operator]
            except KeyError:
                raise InvalidQuery(&quot;Unknown operator %s&quot; % operator)
            assert len(nodes) == 2 # binary operators take 2 values
            def _op(values):
                return op(values[nodes[0]], nodes[1])
            return _op
        # Iterate over every item in the list of the value linked
        # to the logical operator, and compile it down to its own
        # evaluator.
        elements = [self.build_evaluator(node) for node in nodes]
        return lambda values: op((e(values) for e in elements))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can check that it works by building some filters:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;x = Filter({&quot;eq&quot;: (&quot;foo&quot;, 1)})
assert x(foo=1)

x = Filter({&quot;eq&quot;: (&quot;foo&quot;, &quot;bar&quot;)})
assert not x(foo=1)

x = Filter({&quot;or&quot;: (
    {&quot;eq&quot;: (&quot;foo&quot;, &quot;bar&quot;)},
    {&quot;eq&quot;: (&quot;bar&quot;, 1)},
)})
assert x(foo=1, bar=1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Supporting multiple values is handy as it allows to pass complete dictionaries to the filter, rather than just one value. That enables users to filter more complex objects.&lt;/p&gt;
&lt;h2&gt;Sub-dictionary support&lt;/h2&gt;
&lt;p&gt;It&apos;s also possible to support deeper data structure, like a dictionary of dictionary. By replacing &lt;code&gt;values[nodes[0]]&lt;/code&gt; by &lt;code&gt;self._resolve_name(values, node[0])&lt;/code&gt; with a &lt;code&gt;_resolve_name&lt;/code&gt; method like this one, the filter is able to traverse dictionaries:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ATTR_SEPARATOR = &quot;.&quot;

def _resolve_name(self, values, name):
    try:
        for subname in name.split(self.ATTR_SEPARATOR):
            values = values[subname]
        return values
    except KeyError:
        raise InvalidQuery(&quot;Unknown attribute %s&quot; % name)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It then works like that:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;x = Filter({&quot;eq&quot;: (&quot;baz.sub&quot;, 23)})
assert x(foo=1, bar=1, baz={&quot;sub&quot;: 23})

x = Filter({&quot;eq&quot;: (&quot;baz.sub&quot;, 23)})
assert not x(foo=1, bar=1, baz={&quot;sub&quot;: 3})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By using the syntax &lt;code&gt;key.subkey.subsubkey&lt;/code&gt; the filter is able to access item inside dictionaries on more complex data structure.&lt;/p&gt;
&lt;p&gt;That basic filter engine can evolve quite easily in something powerful, as you can add new operators or new way to access/manipulate the passed data structure.&lt;/p&gt;
&lt;p&gt;If you have other ideas on nifty features that could be added, feel free to add a comment below!&lt;/p&gt;
</content:encoded></item><item><title>The Best flake8 Extensions for your Python Project</title><link>https://julien.danjou.info/blog/the-best-flake8-extensions/</link><guid isPermaLink="true">https://julien.danjou.info/blog/the-best-flake8-extensions/</guid><description>In the last blog post about coding style, we dissected what the state of the art was regarding coding style check in Python.</description><pubDate>Mon, 05 Nov 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In the &lt;a href=&quot;https://julien.danjou.info/blog/code-style-checks-in-python&quot;&gt;last blog post about coding style&lt;/a&gt;, we dissected what the state of the art was regarding coding style check in Python.&lt;/p&gt;
&lt;p&gt;As we&apos;ve seen, Flake8 is a wrapper around several tools and is extensible via plugins: meaning that you can add your own checks. I&apos;m a heavy user of Flake8 and relies on a few plugins to extend the check coverage of common programming mistakes in Python. Here&apos;s the list of the ones I can&apos;t work without. As a bonus, you&apos;ll find at the end of this post, a sample of my go-to &lt;code&gt;tox.ini&lt;/code&gt; file.&lt;/p&gt;
&lt;h2&gt;flake8-import-order&lt;/h2&gt;
&lt;p&gt;The name is quite explicit: this extension checks the order of your &lt;code&gt;import&lt;/code&gt; statements at the beginning of your files. By default, it uses a style that I enjoy, which looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import os
import sys

import requests

import yaml

import myproject
from myproject.utils import somemodule
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The builtin modules are grouped as the first ones. Then comes a group for each third-party modules that are imported. Finally, the last group manages the modules of the current project. I find this way of organizing modules import quite clear and easy to read.&lt;/p&gt;
&lt;p&gt;To make sure flake8-import-order knows about the name of your project module name, you need to specify it in &lt;code&gt;tox.ini&lt;/code&gt; with the &lt;code&gt;application-import-names&lt;/code&gt; option.&lt;/p&gt;
&lt;p&gt;If you beg to differ, you can use &lt;a href=&quot;https://github.com/PyCQA/flake8-import-order/#styles&quot;&gt;any of the other styles that flake8-import-order offers by default&lt;/a&gt; by setting the &lt;code&gt;import-order-style&lt;/code&gt; option. You can obviously &lt;a href=&quot;https://github.com/PyCQA/flake8-import-order/#extending-styles&quot;&gt;provide your own style&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;flake8-blind-except&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/elijahandrews/flake8-blind-except&quot;&gt;flake8-blind-except extension&lt;/a&gt; checks that no &lt;code&gt;except&lt;/code&gt; statement is used without specifying an exception type. The following excerpt is, therefore, considered invalid:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;try:
    do_something()
except:
    pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using &lt;code&gt;except&lt;/code&gt; without any exception type specified is considered bad practice as it might catch unwanted exceptions. It forces the developer to think about what kind of errors might happen and should &lt;em&gt;really&lt;/em&gt; be caught.&lt;/p&gt;
&lt;p&gt;In the rare case any exception should be caught, it&apos;s still possible to use &lt;code&gt;except Exception&lt;/code&gt; anyway.&lt;/p&gt;
&lt;h2&gt;flake8-builtins&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/gforcada/flake8-builtins&quot;&gt;flake8-builtins plugin&lt;/a&gt; checks that there is no name collision between your code and the Python builtin variables.&lt;/p&gt;
&lt;p&gt;For example, this code would trigger an error:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def first(list):
    return list[0]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As &lt;code&gt;list&lt;/code&gt; is a builtin in Python (to create a list!), shadowing its definition by using &lt;code&gt;list&lt;/code&gt; as the name of a parameter in a function signature would trigger a warning from flake8-builtins.&lt;/p&gt;
&lt;p&gt;While the code is valid, it&apos;s a bad habit to override Python builtins functions. It might lead to tricky errors; in the above example, if you ever need to call &lt;code&gt;list()&lt;/code&gt;, you won&apos;t be able to.&lt;/p&gt;
&lt;h2&gt;flake8-logging-format&lt;/h2&gt;
&lt;p&gt;This &lt;a href=&quot;https://github.com/globality-corp/flake8-logging-format&quot;&gt;module&lt;/a&gt; is handy as it is still slapping my fingers once in a while. When using the &lt;code&gt;logging&lt;/code&gt; module, it prevents from writing this kind of code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mylogger.info(&quot;Hello %s&quot; % mystring)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While this works, it&apos;s suboptimal as it forces the string interpolation. If the logger is configured to print only messages with a logging level of warning or above, doing a string interpolation here is pointless.&lt;/p&gt;
&lt;p&gt;Therefore, one should instead write:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mylogger.info(&quot;Hello %s&quot;, mystring)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same goes if you use &lt;code&gt;format&lt;/code&gt; to do any formatting.&lt;/p&gt;
&lt;p&gt;Be aware that contrary to other flake8 modules, this one does not enable the check by default. You&apos;ll need to add &lt;code&gt;enable-extensions=G&lt;/code&gt; in your &lt;code&gt;tox.ini&lt;/code&gt; file.&lt;/p&gt;
&lt;h2&gt;flake8-docstrings&lt;/h2&gt;
&lt;p&gt;The f&lt;a href=&quot;https://gitlab.com/pycqa/flake8-docstringshttps://gitlab.com/pycqa/flake8-docstrings&quot;&gt;lake8-docstrings&lt;/a&gt; module checks the content of your Python docstrings for respect of the &lt;a href=&quot;https://www.python.org/dev/peps/pep-0257/&quot;&gt;PEP 257&lt;/a&gt;. This PEP is full of small details about formatting your docstrings the right way, which is something you wouldn&apos;t be able to do without such a tool. A simple example would be:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Foobar:
    &quot;&quot;&quot;A foobar&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While this seems valid, there is a missing point at the end of the docstring.&lt;/p&gt;
&lt;p&gt;Trust me, especially if you are writing a library that is consumed by other developers, this is a must-have.&lt;/p&gt;
&lt;h2&gt;flake8-rst-docstrings&lt;/h2&gt;
&lt;p&gt;This &lt;a href=&quot;https://pypi.org/project/flake8-rst-docstrings/&quot;&gt;extension&lt;/a&gt; is a good complement to flake8-docstrings: it checks that the content of your docstrings is valid RST. It&apos;s a no-brainer, so I&apos;d install it without question. Again, if your project exports a documented API that is built with &lt;a href=&quot;https://sphinx-doc.org&quot;&gt;Sphinx&lt;/a&gt;, this is a must-have.&lt;/p&gt;
&lt;h2&gt;My standard tox.ini&lt;/h2&gt;
&lt;p&gt;Here&apos;s the standard &lt;code&gt;tox.ini&lt;/code&gt; excerpt that I use in most of my projects. You can copy paste it and use&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[testenv:pep8]
deps = flake8
       flake8-import-order
       flake8-blind-except
       flake8-builtins
       flake8-docstrings
       flake8-rst-docstrings
       flake8-logging-format
commands = flake8

[flake8]
exclude = .tox
## If you need to ignore some error codes in the whole source code
## you can write them here
## ignore = D100,D101
show-source = true
enable-extensions=G
application-import-names = &amp;lt;myprojectname&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before disabling an error code for your entire project, remember that you can force flake8 to ignore a particular instance of the error by adding the &lt;code&gt;# noqa&lt;/code&gt; tag at the end of the line.&lt;/p&gt;
&lt;p&gt;If you have any flake8 extension that you think is useful, please let me know in the comment section!&lt;/p&gt;
</content:encoded></item><item><title>More GitHub workflow automation</title><link>https://julien.danjou.info/blog/automating-github-workflows/</link><guid isPermaLink="true">https://julien.danjou.info/blog/automating-github-workflows/</guid><description>The more you use computers, the more you see the potentials for automating everything. Who doesn&apos;t love that?</description><pubDate>Tue, 16 Oct 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The more you use computers, the more you see the potentials for automating everything. Who doesn&apos;t love that? By building &lt;a href=&quot;https://mergify.io&quot;&gt;Mergify&lt;/a&gt; those last months, we&apos;ve decided it was time bring more automation to the development workflow.&lt;/p&gt;
&lt;p&gt;Mergify&apos;s first version was a &lt;em&gt;minimal viable product&lt;/em&gt; around automating the merge of pull requests. As &lt;a href=&quot;https://julien.danjou.info/blog/stop-merging-your-pull-request-manually&quot;&gt;I wrote a few months ago&lt;/a&gt;, we wanted to automate the merge of pull requests when it was ready to be merged. For most projects, this is easy and consists of a simple rule: &quot;it must be approved by a developer and pass the CI&quot;.&lt;/p&gt;
&lt;h2&gt;Evolving on Feedback&lt;/h2&gt;
&lt;p&gt;For the first few months, we received a lot of feedback from our users. They were enthusiastic about the product but were frustrated by a couple of things.&lt;/p&gt;
&lt;p&gt;First, Mergify would mess up with branch protections. We thought that people wanted the GitHub UI to match their rules. As I&apos;ll explain later, it turns out to be only partially true, and we found a workaround.&lt;/p&gt;
&lt;p&gt;Then, Mergify&apos;s abilities were capped by some of the limitations of the GitHub workflow and API. For example, GitHub would only allow rules per branch, whereas our users wanted to have rules applied based on a lot of different criteria.&lt;/p&gt;
&lt;h2&gt;Building the Next Engine&lt;/h2&gt;
&lt;p&gt;We rolled up our sleeves and started to build that new engine. The first thing was to get rid of the GitHub branch protection feature altogether and leveraging the Checks API to render something useful to the users in the UI. You can now have a complete overview of the rules that will be applied to your pull requests in the UI, making it easy to understand what&apos;s happening.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/10/1_6XA_yUbEHkgBs86cn31yOw.png&quot; alt=&quot;Screenshot of Mergify Checks API showing rule overview in the GitHub UI&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Then, we wrote a new matching engine that would allow matching any pull requests based on any of its attributes. You can now automate your workflow with a finer-grained configuration.&lt;/p&gt;
&lt;h2&gt;What Does It Look Like?&lt;/h2&gt;
&lt;p&gt;Here&apos;s a simple rule you could write:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pull_request_rules:
  - name: automatic merge on approval and CI pass
    conditions:
     - &quot;#approved-reviews-by&amp;gt;=1&quot;
     - status-success=continuous-integration/travis-ci/pr
     - label!=work-in-progress
    actions:
      merge:
        method: merge
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that, any pull request that has been approved by a collaborator, passes the Travis CI job and does not have the label &lt;code&gt;work-in-progress&lt;/code&gt; will be automatically merged by Mergify.&lt;/p&gt;
&lt;p&gt;You could use even more &lt;a href=&quot;https://docs.mergify.io/actions/&quot;&gt;actions&lt;/a&gt; to backport this pull request to another branch, close the pull request or add/remove labels. We&apos;re starting to see users building amazing workflow with that engine!&lt;/p&gt;
&lt;p&gt;We&apos;re thrilled by this new version we launched this week and glad we&apos;re getting amazing feedback (again) from our users.&lt;/p&gt;
&lt;p&gt;When you give it a try, drop me a note and let me know what you think about it!&lt;/p&gt;
</content:encoded></item><item><title>Code Style Checks in Python</title><link>https://julien.danjou.info/blog/code-style-checks-in-python/</link><guid isPermaLink="true">https://julien.danjou.info/blog/code-style-checks-in-python/</guid><description>After starting your first Python project, you might realize that it is actually not that obvious to be consistent with the way you write Python code.</description><pubDate>Mon, 01 Oct 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;After &lt;a href=&quot;https://julien.danjou.info/blog/starting-your-first-python-project&quot;&gt;starting your first Python project&lt;/a&gt;, you might realize that it is actually not that obvious to be consistent with the way you write Python code. If you collaborate with other developers, your code style might differ, and the code can become somehow unreadable.&lt;/p&gt;
&lt;p&gt;I hate coding style discussions as much as every engineer. Who has not seen hours of nitpicking on code reviews, a heated debate around the coffee machine or nerf guns battles to decide where the semicolon should be?&lt;/p&gt;
&lt;p&gt;When I start a new project, the first thing I do is set up an automated style check. With that in place, there&apos;s no time wasted during code reviews about manually checking what&apos;s a program&apos;s good at: coding style consistency. Since coding style is a touchy subject, it&apos;s a good reason to tackle it at the beginning of the project.&lt;/p&gt;
&lt;p&gt;Python has an amazing quality that few other languages have: it uses indentation to define blocks. While it offers a solution to the age-old question of &quot;where should I put my curly braces?&quot;, it introduces a new question in the process: &quot;how should I indent?&quot;.&lt;/p&gt;
&lt;p&gt;I imagine that it was one of the first question that was raised in the community, so the Python folks, in their vast wisdom, came up with the &lt;a href=&quot;http://www.python.org/dev/peps/pep-0008/&quot;&gt;PEP 8&lt;/a&gt;: &lt;em&gt;Style Guide for Python Code&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This document defines the standard style for writing Python code. The list of guidelines boils down to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use 4 spaces per indentation level.&lt;/li&gt;
&lt;li&gt;Limit all lines to a maximum of 79 characters.&lt;/li&gt;
&lt;li&gt;Separate top-level function and class definitions with two blank lines.&lt;/li&gt;
&lt;li&gt;Encode files using ASCII or UTF-8.&lt;/li&gt;
&lt;li&gt;One module import per &lt;code&gt;import&lt;/code&gt; statement and per line, at the top of the file, after comments and docstrings, grouped first by standard, then third-party, and finally local library imports.&lt;/li&gt;
&lt;li&gt;No extraneous whitespaces between parentheses, brackets, or braces, or before commas.&lt;/li&gt;
&lt;li&gt;Name classes in &lt;code&gt;CamelCase&lt;/code&gt;; suffix exceptions with &lt;code&gt;Error&lt;/code&gt; (if applicable); name functions in lowercase with words &lt;code&gt;separated_by_underscores&lt;/code&gt;; and use a leading underscore for &lt;code&gt;_private&lt;/code&gt; attributes or methods.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These guidelines really aren&apos;t hard to follow and they make a lot of sense. Most Python programmers have no trouble sticking to them as they write code.&lt;/p&gt;
&lt;p&gt;However, &lt;em&gt;errare humanum est&lt;/em&gt;, and it&apos;s still a pain to look through your code to make sure it fits the PEP 8 guidelines. That&apos;s what the &lt;a href=&quot;http://pycodestyle.pycqa.org/en/latest/&quot;&gt;pycodestyle&lt;/a&gt; tool (formerly called &lt;em&gt;pep8&lt;/em&gt;) is there for: it can automatically check any Python file you send its way.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ pycodestyle hello.py
hello.py:4:1: E302 expected 2 blank lines, found 1
$ echo $?
1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;pycodestyle&lt;/em&gt; indicates which lines and columns do not conform to PEP 8 and reports each issue with a code. Violations of &lt;em&gt;MUST&lt;/em&gt; statements in the specification are reported as &lt;em&gt;errors&lt;/em&gt; — their error codes start with an &lt;em&gt;E&lt;/em&gt;. Minor issues are reported as &lt;em&gt;warnings&lt;/em&gt; — their error codes start with a &lt;em&gt;W&lt;/em&gt;. The three-digit code following the first letter indicates the exact kind of error or warning.&lt;/p&gt;
&lt;p&gt;You can tell the general category of an error code at a glance by looking at the hundreds digit: for example, errors starting with &lt;code&gt;E2&lt;/code&gt; indicate issues with whitespace; errors starting with &lt;code&gt;E3&lt;/code&gt; indicate issues with blank lines; and warnings starting with &lt;code&gt;W6&lt;/code&gt; indicate deprecated features being used.&lt;/p&gt;
&lt;p&gt;I advise you to consider it and run a PEP 8 validation tool against your source code on a regular basis. An easy way to do this is to integrate it into your continuous integration system: it&apos;s a good way to ensure that you continue to respect the PEP 8 guidelines in the long term.&lt;/p&gt;
&lt;p&gt;Most open source project enforce PEP 8 conformance through automatic checks. Doing so since the beginning of the project might frustrate newcomers, but it also ensures that the codebase always looks the same in every part of the project. This is very important for a project of any size where there are multiple developers with differing opinions on whitespace ordering. You know what I mean.&lt;/p&gt;
&lt;p&gt;It&apos;s also possible to ignore certain kinds of errors and warnings by using the &lt;code&gt;--ignore&lt;/code&gt; option:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ pycodestyle --ignore=E3 hello.py
$ echo $?
0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This allows you to effectively ignore parts of the PEP 8 specification that you don&apos;t want to follow. If you&apos;re running &lt;em&gt;pycodestyle&lt;/em&gt; on a existing code base, it also allows you to ignore certain kinds of problems so you can focus on fixing issues one category at a time.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you write C code for Python (e.g. modules), the &lt;a href=&quot;http://www.python.org/dev/peps/pep-0007/&quot;&gt;PEP 7&lt;/a&gt; standard describes the coding style that you should follow.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Other tools also exist that check for actual coding errors rather than style errors. Some notable examples include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://launchpad.net/pyflakes&quot;&gt;pyflakes&lt;/a&gt;, which is also extendable via plugins.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pypi.python.org/pypi/pylint&quot;&gt;pylint&lt;/a&gt;, which also checks PEP 8 conformance while performing more checks by default. It also can be extended via plugins.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These tools all make use of static analysis — that is, they parse the code and analyze it rather than running it outright.&lt;/p&gt;
&lt;p&gt;If you choose to use &lt;em&gt;pyflakes&lt;/em&gt; — which I recommend — note that it doesn&apos;t check PEP 8 conformance on its own — you would still &lt;em&gt;pycodestyle&lt;/em&gt; to do that. That means you need 2 different tools to have a proper coverage.&lt;/p&gt;
&lt;p&gt;In order to simplify things, a project named &lt;em&gt;&lt;a href=&quot;https://pypi.python.org/pypi/flake8&quot;&gt;flake8&lt;/a&gt;&lt;/em&gt; exists and combines &lt;em&gt;pyflakes&lt;/em&gt; and &lt;em&gt;pycodestyle&lt;/em&gt; into a single command. It also adds some new fancy features: for example, it can skip checks on lines containing &lt;code&gt;# noqa&lt;/code&gt; and is extensible via plugins.&lt;/p&gt;
&lt;p&gt;There are a large number of plugins available for &lt;em&gt;flake8&lt;/em&gt; that you can just use. For example, installing &lt;em&gt;flake8-import-order&lt;/em&gt; (with &lt;code&gt;pip install flake8-import-order&lt;/code&gt;) will extend &lt;em&gt;flake8&lt;/em&gt; so it also checks that your &lt;code&gt;import&lt;/code&gt; statements are sorted alphabetically in your source code.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;flake8&lt;/em&gt; is now heavily used in most open source projects for code style verification. Some large open source projects even wrote their own plugins, adding checks checks for errors such as odd usage of &lt;code&gt;except&lt;/code&gt;, Python 2/3 portability issues, import style, dangerous string formatting, possible localization issues, etc.&lt;/p&gt;
&lt;p&gt;If you&apos;re starting a new project, I strongly recommend you use one of these tools and rely on it for automatic checking of your code quality and style. If you already have a codebase, a good approach is to run them with most of the warnings disabled and fix issues one category at a time.&lt;/p&gt;
&lt;p&gt;While none of these tools may be a perfect fit for your project or your preferences, using &lt;em&gt;flake8&lt;/em&gt; together is a good way to improve the quality of your code and make it more durable. If nothing else, it&apos;s a good start toward that goal.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Many text editors, including the famous &lt;a href=&quot;http://www.gnu.org/software/emacs/&quot;&gt;GNU Emacs&lt;/a&gt; and &lt;a href=&quot;http://www.vim.org/&quot;&gt;vim&lt;/a&gt;, have plugins available (such as &lt;em&gt;Flycheck&lt;/em&gt;) that can run tools such as &lt;em&gt;pep8&lt;/em&gt; or &lt;em&gt;flake8&lt;/em&gt; directly in your code buffer, interactively highlighting any part of your code that isn&apos;t PEP 8-compliant. This is a handy way to fix most style errors as you write your code.&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>High-Performance in Python with Zero-Copy and the Buffer Protocol</title><link>https://julien.danjou.info/blog/high-performance-in-python-with-zero-copy-and-the-buffer-protocol/</link><guid isPermaLink="true">https://julien.danjou.info/blog/high-performance-in-python-with-zero-copy-and-the-buffer-protocol/</guid><description>Whatever your programs are doing, they often have to deal with vast amounts of data. This data is usually represented and manipulated in the form of strings. However, handling such a large quantity of</description><pubDate>Mon, 03 Sep 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Whatever your programs are doing, they often have to deal with vast amounts of data. This data is usually represented and manipulated in the form of &lt;em&gt;strings&lt;/em&gt;. However, handling such a large quantity of input in strings can be very ineffective once you start manipulating them by copying, slicing, and modifying. Why?&lt;/p&gt;
&lt;p&gt;Let&apos;s consider a small program which reads a large file of binary data, and&lt;br /&gt;
copies it partially into another file. To examine out the memory usage of this program, we will use &lt;a href=&quot;https://pypi.python.org/pypi/memory_profiler&quot;&gt;memory_profiler&lt;/a&gt;, an excellent Python package that allows us to see the memory usage of a program line by line.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@profile
def read_random():
    with open(&quot;/dev/urandom&quot;, &quot;rb&quot;) as source:
        content = source.read(1024 * 10000)
        content_to_write = content[1024:]
    print(&quot;Content length: %d, content to write length %d&quot; %
          (len(content), len(content_to_write)))
    with open(&quot;/dev/null&quot;, &quot;wb&quot;) as target:
        target.write(content_to_write)

if __name__ == &apos;__main__&apos;:
    read_random()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running the above program using &lt;em&gt;memory_profiler&lt;/em&gt; produces the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python -m memory_profiler memoryview/copy.py
Content length: 10240000, content to write length 10238976
Filename: memoryview/copy.py

Mem usage    Increment   Line Contents
======================================
                         @profile
 9.883 MB     0.000 MB   def read_random():
 9.887 MB     0.004 MB       with open(&quot;/dev/urandom&quot;, &quot;rb&quot;) as source:
19.656 MB     9.770 MB           content = source.read(1024 * 10000)
29.422 MB     9.766 MB           content_to_write = content[1024:]
29.422 MB     0.000 MB       print(&quot;Content length: %d, content to write length %d&quot; %
29.434 MB     0.012 MB             (len(content), len(content_to_write)))
29.434 MB     0.000 MB       with open(&quot;/dev/null&quot;, &quot;wb&quot;) as target:
29.434 MB     0.000 MB           target.write(content_to_write)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The call to &lt;code&gt;source.read&lt;/code&gt; reads 10 MB from &lt;code&gt;/dev/urandom&lt;/code&gt;. Python needs to allocate around 10 MB of memory to store this data as a string. The instruction on the line just after, &lt;code&gt;content[1024:]&lt;/code&gt;, copies the entire block of data minus the first KB — allocating 10 more megabytes.&lt;/p&gt;
&lt;p&gt;So what&apos;s interesting here, is to notice that the memory usage of the program increased by about 10 MB when building the variable &lt;code&gt;content_to_write&lt;/code&gt;. The slice operator is copying the entirety of &lt;code&gt;content&lt;/code&gt;, minus the first KB, into a new string object.&lt;/p&gt;
&lt;p&gt;When dealing with extensive data, performing this kind of operation on large byte arrays is going to be a disaster. If you already have written C code, you know that using &lt;code&gt;memcpy()&lt;/code&gt; has a significant cost, both in term of memory usage and regarding general performance: copying memory is slow.&lt;/p&gt;
&lt;p&gt;However, as a C programmer, you also know that strings are arrays of characters and that nothing stops you from looking at only part of this array without copying it, through the use of basic pointer arithmetic – assuming that the entire string is in a contiguous memory area.&lt;/p&gt;
&lt;p&gt;This is possible in Python using objects which implement the &lt;em&gt;buffer protocol&lt;/em&gt;. The buffer protocol is defined in &lt;a href=&quot;http://www.python.org/dev/peps/pep-3118/&quot;&gt;PEP 3118&lt;/a&gt;, which explains the C API used to provide this protocol to various types, such as strings.&lt;/p&gt;
&lt;p&gt;When an object implements this protocol, you can use the &lt;code&gt;memoryview&lt;/code&gt; class constructor on it to build a new &lt;em&gt;memoryview&lt;/em&gt; object that references the original object memory.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; s = b&quot;abcdefgh&quot;
&amp;gt;&amp;gt;&amp;gt; view = memoryview(s)
&amp;gt;&amp;gt;&amp;gt; view[1]
98
&amp;gt;&amp;gt;&amp;gt; limited = view[1:3]
&amp;gt;&amp;gt;&amp;gt; limited
&amp;lt;memory at 0x7fca18b8d460&amp;gt;
&amp;gt;&amp;gt;&amp;gt; bytes(view[1:3])
b&apos;bc&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: &lt;code&gt;98&lt;/code&gt; is the ASCII code for the letter &lt;code&gt;b&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In the example above, we use the fact that the &lt;code&gt;memoryview&lt;/code&gt; object&apos;s slice operator itself returns a &lt;code&gt;memoryview&lt;/code&gt; object. That means it does &lt;strong&gt;not&lt;/strong&gt; copy any data but merely references a particular slice of it.&lt;/p&gt;
&lt;p&gt;The graph below illustrates what happens:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/08/serious-python__3.png&quot; alt=&quot;serious-python__3&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Therefore, it is possible to rewrite the program above in a more efficient manner. We need to reference the data that we want to write using a &lt;em&gt;memoryview&lt;/em&gt; object, rather than allocating a new string.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@profile
def read_random():
    with open(&quot;/dev/urandom&quot;, &quot;rb&quot;) as source:
        content = source.read(1024 * 10000)
        content_to_write = memoryview(content)[1024:]
    print(&quot;Content length: %d, content to write length %d&quot; %
          (len(content), len(content_to_write)))
    with open(&quot;/dev/null&quot;, &quot;wb&quot;) as target:
        target.write(content_to_write)

if __name__ == &apos;__main__&apos;:
    read_random()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s run the program above with the memory profiler:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python -m memory_profiler memoryview/copy-memoryview.py
Content length: 10240000, content to write length 10238976
Filename: memoryview/copy-memoryview.py

Mem usage    Increment   Line Contents
======================================
                         @profile
 9.887 MB     0.000 MB   def read_random():
 9.891 MB     0.004 MB       with open(&quot;/dev/urandom&quot;, &quot;rb&quot;) as source:
19.660 MB     9.770 MB           content = source.read(1024 * 10000) &amp;lt;1&amp;gt;
19.660 MB     0.000 MB           content_to_write = memoryview(content)[1024:] &amp;lt;2&amp;gt;
19.660 MB     0.000 MB       print(&quot;Content length: %d, content to write length %d&quot; %
19.672 MB     0.012 MB             (len(content), len(content_to_write)))
19.672 MB     0.000 MB       with open(&quot;/dev/null&quot;, &quot;wb&quot;) as target:
19.672 MB     0.000 MB           target.write(content_to_write)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In that case, the &lt;code&gt;source.read&lt;/code&gt; call still allocates 10 MB of memory to read the content of the file. However, when using &lt;code&gt;memoryview&lt;/code&gt; to refer to the offset content, no more memory is allocated.&lt;/p&gt;
&lt;p&gt;This version of the program ends up allocating 50% less memory than the original version!&lt;/p&gt;
&lt;p&gt;This kind of trick is especially useful when dealing with sockets. When sending data over a socket, all the data might not be sent in a single call.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import socket
s = socket.socket(…)
s.connect(…)
## Build a bytes object with more than 100 millions times the letter `a`
data = b&quot;a&quot; * (1024 * 100000)
while data:
    sent = s.send(data)
    # Remove the first `sent` bytes sent
    data = data[sent:] &amp;lt;2&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using a mechanism as implemented above, the program copies the data over and over until the socket has sent everything. By using &lt;code&gt;memoryview&lt;/code&gt;, it is possible to achieve the same functionality with zero-copy, and therefore higher performance:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import socket
s = socket.socket(…)
s.connect(…)
## Build a bytes object with more than 100 millions times the letter `a`
data = b&quot;a&quot; * (1024 * 100000)
mv = memoryview(data)
while mv:
    sent = s.send(mv)
    # Build a new memoryview object pointing to the data which remains to be sent
    mv = mv[sent:]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As this won&apos;t copy anything, it won&apos;t use any more memory than the 100 MB&lt;br /&gt;
initially needed for the &lt;code&gt;data&lt;/code&gt; variable.&lt;/p&gt;
&lt;p&gt;So far we&apos;ve used &lt;code&gt;memoryview&lt;/code&gt; objects to write data efficiently, but the same method can also be used to read data. Most I/O operations in Python know how to deal with objects implementing the buffer protocol. They can read from it, but also write to it. In this case, we don&apos;t need &lt;code&gt;memoryview&lt;/code&gt; objects – we can ask an I/O function to write into our pre-allocated object:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; ba = bytearray(8)
&amp;gt;&amp;gt;&amp;gt; ba
bytearray(b&apos;\x00\x00\x00\x00\x00\x00\x00\x00&apos;)
&amp;gt;&amp;gt;&amp;gt; with open(&quot;/dev/urandom&quot;, &quot;rb&quot;) as source:
...     source.readinto(ba)
... 
8
&amp;gt;&amp;gt;&amp;gt; ba
bytearray(b&apos;`m.z\x8d\x0fp\xa1&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With such techniques, it&apos;s easy to pre-allocate a buffer (as you would do in C to mitigate the number of calls to &lt;code&gt;malloc()&lt;/code&gt;) and fill it at your convenience.&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;memoryview&lt;/code&gt;, you can even place data at any point in the memory area:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; ba = bytearray(8)
&amp;gt;&amp;gt;&amp;gt; # Reference the _bytearray_ from offset 4 to its end
&amp;gt;&amp;gt;&amp;gt; ba_at_4 = memoryview(ba)[4:]
&amp;gt;&amp;gt;&amp;gt; with open(&quot;/dev/urandom&quot;, &quot;rb&quot;) as source:
... # Write the content of /dev/urandom from offset 4 to the end of the
... # bytearray, effectively reading 4 bytes only
...     source.readinto(ba_at_4)
... 
4
&amp;gt;&amp;gt;&amp;gt; ba
bytearray(b&apos;\x00\x00\x00\x00\x0b\x19\xae\xb2&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The buffer protocol is fundamental to achieve low memory overhead and great performances. As Python hides all the memory allocations, developers tend to forget what happens under the hood, at a high cost for the speed of their programs!&lt;/p&gt;
&lt;p&gt;It&apos;s also good to know that both the objects in the &lt;code&gt;array&lt;/code&gt; module and the functions in the &lt;code&gt;struct&lt;/code&gt; module can handle the buffer protocol correctly, and can, therefore, efficiently perform when targeting zero copy.&lt;/p&gt;
</content:encoded></item><item><title>Gnocchi 4.3.0 released</title><link>https://julien.danjou.info/blog/gnocchi-4-3-0-released/</link><guid isPermaLink="true">https://julien.danjou.info/blog/gnocchi-4-3-0-released/</guid><description>This new release minor release of Gnocchi has been a bit longer than usual, but here it is!</description><pubDate>Mon, 30 Jul 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This new release minor release of Gnocchi has been a bit longer than usual, but here it is!&lt;/p&gt;
&lt;p&gt;So what&apos;s new in this version of Gnocchi? Well, according to &lt;a href=&quot;https://gnocchi.xyz/releasenotes/4.3.html&quot;&gt;the release notes&lt;/a&gt;, not much. There are only two new features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;gnocchi-injector&lt;/em&gt; which allows injecting data for &lt;em&gt;metricd&lt;/em&gt; consumption directly. This is useful to test &lt;em&gt;metricd&lt;/em&gt; performances.&lt;/li&gt;
&lt;li&gt;The ability for the &lt;code&gt;/v1/aggregation/resources&lt;/code&gt; endpoint to read a string rather than a JSON formatted payload for filtering.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nothing exciting here… however, other changes are not user-visible and are not in those notes:&lt;/p&gt;
&lt;p&gt;Performance boost, everywhere!&lt;/p&gt;
&lt;p&gt;The storage engine has been largely improved to batch a ton of operations that used to be done on a per-metric basis. When ingesting new measures, Gnocchi was storing those new points in batch. However, the processing done by &lt;em&gt;metricd&lt;/em&gt; later was single-metric based for most it. This did not leverage the efficiency that some backend might have and would create more I/O operations than necessary.&lt;/p&gt;
&lt;p&gt;Each incoming data sack is now processed in batch mode, making &lt;em&gt;metricd&lt;/em&gt; much faster at aggregating metrics data! When doing local benchmarks, some scenario presented an improvement of 8x.&lt;/p&gt;
&lt;p&gt;This new storage internal API is not used by the REST API yet, as many operations exposed by the API are oriented for a single metric. That might be a significant improvement for the next version of Gnocchi&apos;s API.&lt;/p&gt;
&lt;p&gt;Happy upgrade!&lt;/p&gt;
</content:encoded></item><item><title>Starting your first Python project</title><link>https://julien.danjou.info/blog/starting-your-first-python-project/</link><guid isPermaLink="true">https://julien.danjou.info/blog/starting-your-first-python-project/</guid><description>There&apos;s a gap between learning the syntax of the Python programming language and being able to build a project from scratch. When you finish reading your first tutorial or book about Python, you&apos;re go</description><pubDate>Thu, 26 Jul 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;There&apos;s a gap between learning the syntax of the Python programming language and being able to build a project from scratch. When you finish reading your first tutorial or book about Python, you&apos;re good to go for writing a Fibonacci suite calculator, but that does not help you starting your &lt;em&gt;actual&lt;/em&gt; project.&lt;/p&gt;
&lt;p&gt;There are a few questions that pop up in your mind, and that&apos;s normal. Let&apos;s take a stab at those!&lt;/p&gt;
&lt;h3&gt;Which Python version should I use?&lt;/h3&gt;
&lt;p&gt;It&apos;s not a secret that Python has several versions that are supported at the same time. Each minor version of the interpreter gets bugfix support for 18 months and security support for 5 years. For example, Python 3.7, released on 27th June 2018, will be supported until Python 3.8 is released, around October 2019 (15 months later). Around December 2019, the last bugfix release of Python 3.7 will occur, and everyone is expected to switch to Python 3.8.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/07/python-release-timeline.png&quot; alt=&quot;Current Python 3.7/3.8 release schedule&quot; /&gt;&lt;/p&gt;
&lt;p&gt;That&apos;s important to be aware of as the version of the interpreter will be entirely part of your software lifecycle.&lt;/p&gt;
&lt;p&gt;On top of that, we should take into consideration the Python 2 versus Python 3 question. That still might be an open question for people working with (very) old platforms.&lt;/p&gt;
&lt;p&gt;In the end, the question of which version of Python one should use is well worth asking.&lt;/p&gt;
&lt;p&gt;Here are some short answers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Versions 2.6 and older are really obsolete by now, so you don&apos;t have to worry about supporting them at all. If you intend on supporting these older versions anyway, be warned that you&apos;ll have an even harder time ensuring that your program supports Python 3.x as well. Though you might still run into Python 2.6 on some older systems; if that&apos;s the case, sorry for you!&lt;/li&gt;
&lt;li&gt;Version 2.7 is and will remain the last version of Python 2.x. I don&apos;t think there is a system where Python 3 is not available one way or the other nowadays. So unless you&apos;re doing archeology once again, forget it. Python 2.7 will not be supported after the year 2020, so the last thing you want to do is build a new software based on it.&lt;/li&gt;
&lt;li&gt;Versions 3.7 is the most recent version of the Python 3 branch as of this writing, and that&apos;s the one that you should target. Most recent operating systems ship at least 3.6, so in the case where you&apos;d target those, you can make sure your application also work with 3.7.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Project Layout&lt;/h2&gt;
&lt;p&gt;Starting a new project is always a puzzle. You never know how to organize your files. However, once you have a proper understanding of the best practice out there, it&apos;s pretty simple.&lt;/p&gt;
&lt;p&gt;First, your project structure should be fairly basic. Use packages and hierarchy wisely: a deep hierarchy can be a nightmare to navigate, while a flat hierarchy tends to become bloated.&lt;/p&gt;
&lt;p&gt;Then, avoid making a few common mistakes. Don&apos;t leave unit tests outside the package directory. These tests should be included in a sub-package of your software so that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;They don&apos;t get automatically installed as a &lt;em&gt;tests&lt;/em&gt; top-level module by &lt;em&gt;setuptools&lt;/em&gt; (or some other packaging library) by accident.&lt;/li&gt;
&lt;li&gt;They can be installed and eventually used by other packages to build their unit tests.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following diagram illustrates what a standard file hierarchy should look like:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/07/serious-python__1-3.png&quot; alt=&quot;A Python project files and directories hierarchy&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;setup.py&lt;/code&gt; is the standard name for Python installation script, along with its companion &lt;code&gt;setup.cfg&lt;/code&gt;, which should contain the installation script configuration. When run, &lt;code&gt;setup.py&lt;/code&gt; installs your package using the Python distribution utilities.&lt;/p&gt;
&lt;p&gt;You can also provide valuable information to users in &lt;code&gt;README.rst&lt;/code&gt; (or &lt;code&gt;README.txt&lt;/code&gt;, or whatever filename suits your fancy). Finally, the &lt;code&gt;docs&lt;/code&gt; directory should contain the package&apos;s documentation in &lt;em&gt;reStructuredText&lt;/em&gt; format, that will be consumed by &lt;em&gt;Sphinx&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Packages often have to provide extra data, such as images, shell scripts, and so forth. Unfortunately, there&apos;s no universally accepted standard for where these files should be stored. Just put them wherever makes the most sense for your project: depending on their functions, for example, Web application templates could go in a &lt;code&gt;templates&lt;/code&gt; directory in your package root directory.&lt;/p&gt;
&lt;p&gt;The following top-level directories also frequently appear:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;etc&lt;/code&gt; for sample configuration files.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tools&lt;/code&gt; for shell scripts or related tools.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bin&lt;/code&gt; for binary scripts you&apos;ve written that will be installed by &lt;code&gt;setup.py&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There&apos;s another design issue that I often encounter. When creating files or modules, some developers create them based on the type of code they will store. For example, they would create &lt;code&gt;functions.py&lt;/code&gt; or &lt;code&gt;exceptions.py&lt;/code&gt; files. This is a &lt;em&gt;terrible&lt;/em&gt; approach. It doesn&apos;t help any developer when navigating the code. The code organization doesn&apos;t benefit from this, and it forces readers to jump between files for no good reason. There are a few exceptions, such as libraries, in some instances, because they do expose a complete API for consumers. However, other than that, think twice before doing that in your application.&lt;/p&gt;
&lt;p&gt;Organize your code based on features, not based on types.&lt;/p&gt;
&lt;p&gt;Creating a module directory with just an &lt;code&gt;__init__.py&lt;/code&gt; file in it is also a bad idea. For example, don&apos;t create a directory named &lt;code&gt;hooks&lt;/code&gt; with a single file named &lt;code&gt;hooks/__init__.py&lt;/code&gt; in it where &lt;code&gt;hooks.py&lt;/code&gt; would have been enough instead. If you create a directory, it should contain several other Python files that belong to the category the directory represents.&lt;/p&gt;
&lt;p&gt;Be also very careful about the code that you put in the &lt;code&gt;__init__.py&lt;/code&gt; files: it is going to be called and executed the first time that any of the module contained in the directory is loaded. This can have unwanted side effects. Those &lt;code&gt;__init__.py&lt;/code&gt; files should be empty most of the time unless you know what you&apos;re doing.&lt;/p&gt;
&lt;h2&gt;Version Numbering&lt;/h2&gt;
&lt;p&gt;Software version needs to be stamped to know which one is more recent than another. As every piece of code evolves, it&apos;s a requirement for every project to be able to organize its timeline.&lt;/p&gt;
&lt;p&gt;There is an infinite number of way to organize your version numbers, but &lt;a href=&quot;http://www.python.org/dev/peps/pep-0440/&quot;&gt;PEP 440&lt;/a&gt; introduces a version format that every Python package, and ideally every application, should follow. This way, programs and packages will be able to quickly and reliably identify which versions of your package they require.&lt;/p&gt;
&lt;p&gt;PEP 440 defines the following regular expression format for version numbering:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;N[.N]+[{a|b|c|rc}N][.postN][.devN]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This allows for standard numbering like &lt;code&gt;1.2&lt;/code&gt; or &lt;code&gt;1.2.3&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;However, please do note that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;1.2&lt;/code&gt; is equivalent to &lt;code&gt;1.2.0&lt;/code&gt;; &lt;code&gt;1.3.4&lt;/code&gt; is equivalent to &lt;code&gt;1.3.4.0&lt;/code&gt;, and so forth.&lt;/li&gt;
&lt;li&gt;Versions matching &lt;code&gt;N[.N]+&lt;/code&gt; are considered &lt;em&gt;final releases&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Date-based versions such as &lt;code&gt;2013.06.22&lt;/code&gt; are considered invalid. Automated tools designed to detect PEP 440-format version numbers will (or should) raise an error if they detect a version number greater than or equal to &lt;code&gt;1980&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Final components can also use the following format:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;N[.N]+aN&lt;/code&gt; (e.g. &lt;code&gt;1.2a1&lt;/code&gt;) denotes an &lt;em&gt;alpha&lt;/em&gt; release, a version that might be unstable and missing features.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;N[.N]+bN&lt;/code&gt; (e.g. &lt;code&gt;2.3.1b2&lt;/code&gt;) denotes a &lt;em&gt;beta&lt;/em&gt; release, a version that might be feature-complete but still buggy.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;N[.N]+cN&lt;/code&gt; or &lt;code&gt;N[.N]+rcN&lt;/code&gt; (e.g. &lt;code&gt;0.4rc1&lt;/code&gt;) denotes a &lt;em&gt;(release) candidate&lt;/em&gt;, a version that might be released as the final product unless significant bugs emerge. While the &lt;code&gt;rc&lt;/code&gt; and &lt;code&gt;c&lt;/code&gt; suffixes have the same meaning, if both are used, &lt;code&gt;rc&lt;/code&gt; releases are considered to be newer than &lt;code&gt;c&lt;/code&gt; releases.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These suffixes can also be used:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.postN&lt;/code&gt; (e.g.&lt;code&gt;1.4.post2&lt;/code&gt;) indicates a &lt;em&gt;post-release&lt;/em&gt;. These are typically used to address minor errors in the publication process (e.g. mistakes in release notes). You shouldn&apos;t use &lt;code&gt;.postN&lt;/code&gt; when releasing a bugfix version; instead, you should increment the minor version number.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.devN&lt;/code&gt; (e.g. &lt;code&gt;2.3.4.dev3&lt;/code&gt;) indicates a &lt;em&gt;developmental release&lt;/em&gt;. This suffix is discouraged because it is harder for humans to parse. It indicates a prerelease of the version that it qualifies: e.g. &lt;code&gt;2.3.4.dev3&lt;/code&gt; indicates the third developmental version of the &lt;code&gt;2.3.4&lt;/code&gt; release, before any alpha, beta, candidate or final release.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This scheme should be sufficient for most common use cases.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You might have heard of &lt;a href=&quot;http://semver.org/&quot;&gt;Semantic Versioning&lt;/a&gt;, which provides its own guidelines for version numbering. This specification partially overlaps with PEP 440, but unfortunately, they&apos;re not entirely compatible. For example, Semantic Versioning&apos;s recommendation for prerelease versioning uses a scheme such as &lt;code&gt;1.0.0-alpha+001&lt;/code&gt; that is not compliant with PEP 440.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Many DVCS platforms, such as Git and Mercurial, can generate version numbers using an identifying hash (for Git, refer to &lt;code&gt;git describe&lt;/code&gt;). Unfortunately, this system isn&apos;t compatible with the scheme defined by PEP 440: for one thing, identifying hashes aren&apos;t orderable.&lt;/p&gt;
&lt;p&gt;Those are only some of the first questions you could have. If you have any other one that you would like me to answer, feel free to write a comment below. Some goes if you have any other pieces of advice you&apos;d like to share!&lt;/p&gt;
</content:encoded></item><item><title>How I stopped merging broken code</title><link>https://julien.danjou.info/blog/how-i-stopped-merging-broken-code/</link><guid isPermaLink="true">https://julien.danjou.info/blog/how-i-stopped-merging-broken-code/</guid><description>It&apos;s been a while since I moved all my projects to GitHub. It&apos;s convenient to host Git projects, and the collaboration workflow is smooth.</description><pubDate>Tue, 03 Jul 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It&apos;s been a while since I moved all my projects to &lt;a href=&quot;https://github.com&quot;&gt;GitHub&lt;/a&gt;. It&apos;s convenient to host Git projects, and the collaboration workflow is smooth.&lt;/p&gt;
&lt;p&gt;I love pull requests to merge code. I review them, I send them, I merge them. The fact that you can plug them into a continuous integration system is great and makes sure that you don&apos;t merge code that will break your software. I usually have &lt;a href=&quot;https://travis-ci.com&quot;&gt;Travis-CI&lt;/a&gt; setup and running my unit tests and code style check.&lt;/p&gt;
&lt;p&gt;The problem with the GitHub workflow is that it allows merging untested code.&lt;/p&gt;
&lt;p&gt;What?&lt;/p&gt;
&lt;p&gt;Yes, it does. If you think that your pull requests, all green decorated, are ready to be merged, you&apos;re wrong.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/07/Screen-Shot-2018-06-20-at-17.12.11.png&quot; alt=&quot;This might not be as good as you think&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You see, pull requests on GitHub are marked as valid as soon as the continuous integration system passes and indicates that everything is valid. However, if the target branch (let&apos;s say, &lt;code&gt;master&lt;/code&gt;) is updated while the pull request is opened, nothing forces to retest that pull request with this new &lt;code&gt;master&lt;/code&gt; branch. You think that the code in this pull request works while that might have become false.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/07/new-master-pr-ci-pass.png&quot; alt=&quot;Master moved, the pull request is not up to date though it&apos;s still marked as passing integration.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;So it might be that what went into your &lt;code&gt;master&lt;/code&gt; branch now breaks this not-yet-merged pull request. You&apos;ve no clue. You&apos;ll trust GitHub and press that green merge button, and you&apos;ll break your software. For whatever reason, it&apos;s possible that the test will break.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/07/merge-ci-fail.png&quot; alt=&quot;If the pull request has not been updated with the latest version of its target branch, it might break your integration.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The good news is that&apos;s something that&apos;s solvable with the &lt;em&gt;strict workflow&lt;/em&gt; that &lt;a href=&quot;https://mergify.io&quot;&gt;Mergify&lt;/a&gt; provides. There&apos;s a nice explanation and example in Mergify&apos;s blog post &lt;em&gt;&lt;a href=&quot;https://medium.com/@mergifyio/you-are-merging-untested-code-1b9f1a10d533&quot;&gt;You are merging untested code&lt;/a&gt;&lt;/em&gt; that I advise you to read. What Mergify provides here is a way to serialize the merge of pull requests while making sure that they are always updated with the latest version of their target branch. It makes sure that there&apos;s no way to merge broken code.&lt;/p&gt;
&lt;p&gt;That&apos;s a workflow I&apos;ve now adopted and automatized on all my repositories, and we&apos;ve been using such a workflow for &lt;a href=&quot;https://gnocchi.xyz&quot;&gt;Gnocchi&lt;/a&gt; for more than a year, with great success. Once you start using it, it becomes impossible to go back!&lt;/p&gt;
</content:encoded></item><item><title>Stop merging your pull requests manually</title><link>https://julien.danjou.info/blog/stop-merging-your-pull-request-manually/</link><guid isPermaLink="true">https://julien.danjou.info/blog/stop-merging-your-pull-request-manually/</guid><description>If there&apos;s something that I hate, it&apos;s doing things manually when I know I could automate them. Am I alone in this situation? I doubt so.  Nevertheless, every day, they are thousands of developers usi</description><pubDate>Wed, 20 Jun 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;If there&apos;s something that I hate, it&apos;s doing things manually when I know I could automate them. Am I alone in this situation? I doubt so.&lt;/p&gt;
&lt;p&gt;Nevertheless, every day, they are thousands of developers using &lt;a href=&quot;https://github.com&quot;&gt;GitHub&lt;/a&gt; that are doing the same thing over and over again: they click on this button:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/06/Screen-Shot-2018-06-19-at-18.12.39.png&quot; alt=&quot;Screen-Shot-2018-06-19-at-18.12.39&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This does not make any sense.&lt;/p&gt;
&lt;p&gt;Don&apos;t get me wrong. It makes sense to merge pull requests. It just does not make sense that someone has to push this damn button every time.&lt;/p&gt;
&lt;p&gt;It does not make any sense because every development team in the world has a known list of pre-requisite before they merge a pull request. Those requirements are almost always the same, and it&apos;s something along those lines:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is the test suite passing?&lt;/li&gt;
&lt;li&gt;Is the documentation up to date?&lt;/li&gt;
&lt;li&gt;Does this follow our code style guideline?&lt;/li&gt;
&lt;li&gt;Have N developers reviewed this?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As this list gets longer, the merging process becomes more error-prone. &quot;Oops, John just clicked on the merge button while there were not enough developer that reviewed the patch.&quot; Rings a bell?&lt;/p&gt;
&lt;p&gt;In my team, we&apos;re like every team out there. We know what our criteria to merge some code into our repository are. That&apos;s why we set up a continuous integration system that runs our test suite each time somebody creates a pull request. We also require the code to be reviewed by 2 members of the team before it&apos;s approbated.&lt;/p&gt;
&lt;p&gt;When those conditions are all set, I want the code to be merged.&lt;/p&gt;
&lt;p&gt;Without clicking a single button.&lt;/p&gt;
&lt;p&gt;That&apos;s exactly how &lt;a href=&quot;https://mergify.io&quot;&gt;Mergify&lt;/a&gt; started.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/06/github-branching-1.png&quot; alt=&quot;github-branching-1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://mergify.io&quot;&gt;Mergify&lt;/a&gt; is a service that pushes that merge button for you. You define rules in the &lt;code&gt;.mergify.yml&lt;/code&gt; file of your repository, and when the rules are satisfied, Mergify merges the pull request.&lt;/p&gt;
&lt;p&gt;No need to press any button.&lt;/p&gt;
&lt;p&gt;Take a random pull request, like this one:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/06/Screen-Shot-2018-06-20-at-17.12.11.png&quot; alt=&quot;Screen-Shot-2018-06-20-at-17.12.11&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This comes from a small project that does not have a lot of continuous integration services set up, just Travis. In this pull request, everything&apos;s green: one of the owners reviewed the code, and the tests are passing. Therefore, the code should be already merged: but it&apos;s there, hanging, chilling, waiting for someone to push that merge button. Someday.&lt;/p&gt;
&lt;p&gt;With &lt;a href=&quot;https://mergify.io&quot;&gt;Mergify&lt;/a&gt; enabled, you&apos;d just have to put this &lt;code&gt;.mergify.yml&lt;/code&gt; a the root of the repository:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rules:
  default:
    protection:
      required_status_checks:
        contexts:
          - continuous-integration/travis-ci
      required_pull_request_reviews:
        required_approving_review_count: 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With such a configuration, &lt;a href=&quot;https://mergify.io&quot;&gt;Mergify&lt;/a&gt; enables the desired restrictions, i.e., Travis passes, and at least one project member reviewed the code. As soon as those conditions are positive, the pull request is automatically merged.&lt;/p&gt;
&lt;p&gt;We built &lt;a href=&quot;https://mergify.io&quot;&gt;Mergify&lt;/a&gt; as a &lt;strong&gt;free service for open-source projects&lt;/strong&gt;. The &lt;a href=&quot;https://github.com/mergifyio/mergify-engine&quot;&gt;engine powering the service&lt;/a&gt; is also open-source.&lt;/p&gt;
&lt;p&gt;Now go &lt;a href=&quot;https://mergify.io&quot;&gt;check it out&lt;/a&gt; and stop letting those pull requests hang out one second more. Merge them!&lt;/p&gt;
&lt;p&gt;If you have any question, feel free to ask us or write a comment below! And stay tuned — as Mergify offers a few other features that I can&apos;t wait to talk about!&lt;/p&gt;
</content:encoded></item><item><title>A simple filtering syntax tree in Python</title><link>https://julien.danjou.info/blog/simple-filtering-syntax-tree-in-python/</link><guid isPermaLink="true">https://julien.danjou.info/blog/simple-filtering-syntax-tree-in-python/</guid><description>Working on various pieces of software those last years, I noticed that there&apos;s always a feature that requires implementing some DSL.  The problem with DSL is that it is never the road that you want to</description><pubDate>Thu, 03 May 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Working on various pieces of software those last years, I noticed that there&apos;s always a feature that requires implementing some DSL.&lt;/p&gt;
&lt;p&gt;The problem with DSL is that it is never the road that you want to go. I remember how creating my first DSL was fascinating: after using programming languages for years, I was finally designing my own tiny language!&lt;/p&gt;
&lt;p&gt;A new language that my users would have to learn and master. Oh, it had nothing new, it was a subset of something, inspired by my years of C, Perl or Python, who knows. And that&apos;s the terrible part about DSL: they are an marvelous tradeoff between the power that they give to users, allowing them to define precisely their needs and the cumbersomeness of learning a language that will be useful in only one specific situation.&lt;/p&gt;
&lt;p&gt;In this blog post, I would like to introduce a very unsophisticated way of implementing the syntax tree that could be used as a basis for a DSL. The goal of that syntax tree will be filtering. The problem it will solve is the following: having a piece of data, we want the user to tell us if the data matches their conditions or not.&lt;/p&gt;
&lt;p&gt;To give a concrete example: a machine wants to grant the user the ability to filter the beans that it should keep. What the machine passes to the filter is the size of the current grain, and the filter should return either &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt;, based on the condition defined by the user: for example, only keep beans that are bigger that are between 1 and 2 centimeters or between 4 and 6 centimeters.&lt;/p&gt;
&lt;p&gt;The number of conditions that the users can define could be quite considerable, and we want to provide at least a basic set of predicate operators: &lt;code&gt;equal&lt;/code&gt;, &lt;code&gt;greater than&lt;/code&gt; and &lt;code&gt;lesser than&lt;/code&gt;. We also want the user to be able to combine those, so we&apos;ll add the logical operators &lt;code&gt;or&lt;/code&gt; and &lt;code&gt;and&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A set of conditions can be seen as a tree, where leaves are either predicates, and in that case, do not have children, or are logical operators, and have children. For example, the propositional logic formula &lt;code&gt;φ1 ∨ (φ2 ∨ φ3)&lt;/code&gt; can be represented with as a tree like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/05/Mqs5i-1.png&quot; alt=&quot;Mqs5i-1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Starting with this in mind, it appears that the natural solution is going to be recursive: handle the predicate as terminal, and if the node is a logical operator, recurse over its children.&lt;br /&gt;
Since we will be doing Python, we&apos;re going to use Python to evaluate our syntax tree.&lt;/p&gt;
&lt;p&gt;The simplest way to write a tree in Python is going to be using dictionaries. A dictionary will represent one node and will have only one key and one value: the key will be the name of the operator (&lt;code&gt;equal&lt;/code&gt;, &lt;code&gt;greater than&lt;/code&gt;, &lt;code&gt;or&lt;/code&gt;, &lt;code&gt;and&lt;/code&gt;…) and the value will be the argument of this operator if it is a predicate, or a list of children (as dictionaries) if it is a logical operator.&lt;/p&gt;
&lt;p&gt;For example, to filter our bean, we would create a tree such as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{&quot;or&quot;: [
  {&quot;and&quot;: [
    {&quot;ge&quot;: 1},
    {&quot;le&quot;: 2},
  ]},
  {&quot;and&quot;: [
    {&quot;ge&quot;: 4},
    {&quot;le&quot;: 6},
  ]},
]}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The goal here is to walk through the tree and evaluate each of the leaves of the tree and returning the final result: if we passed &lt;code&gt;5&lt;/code&gt; to this filter, it would return &lt;code&gt;True&lt;/code&gt;, and if we passed &lt;code&gt;10&lt;/code&gt; to this filter, it would return &lt;code&gt;False&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here&apos;s how we could implement a very depthless filter that only handles predicates (for now):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import operator

class InvalidQuery(Exception):
    pass

class Filter(object):
    binary_operators = {
        &quot;eq&quot;: operator.eq,
        &quot;gt&quot;: operator.gt,
        &quot;ge&quot;: operator.ge,
        &quot;lt&quot;: operator.lt,
        &quot;le&quot;: operator.le,
    }

    def __init__(self, tree):
        # Parse the tree and store the evaluator
        self._eval = self.build_evaluator(tree)

    def __call__(self, value):
        # Call the evaluator with the value
        return self._eval(value)

    def build_evaluator(self, tree):
        try:
            # Pick the first item of the dictionary.
            # If the dictionary has multiple keys/values
            # the first one (= random) will be picked.
            # The key is the operator name (e.g. &quot;eq&quot;)
            # and the value is the argument for it
            operator, nodes = list(tree.items())[0]
        except Exception:
            raise InvalidQuery(&quot;Unable to parse tree %s&quot; % tree)
        try:
            # Lookup the operator name
            op = self.binary_operators[operator]
        except KeyError:
            raise InvalidQuery(&quot;Unknown operator %s&quot; % operator)
        # Return a function (lambda) that takes
        # the filtered value as argument and returns
        # the result of the predicate evaluation
        return lambda value: op(value, nodes)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can use this &lt;code&gt;Filter&lt;/code&gt; class by passing a predicate such as &lt;code&gt;{&quot;eq&quot;: 4}&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; f = Filter({&quot;eq&quot;: 4})
&amp;gt;&amp;gt;&amp;gt; f(2)
False
&amp;gt;&amp;gt;&amp;gt; f(4)
True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This &lt;code&gt;Filter&lt;/code&gt; class works but is quite limited as we did not provide logical operators. Here&apos;s a complete implementation that supports binary operators &lt;code&gt;and&lt;/code&gt; and &lt;code&gt;or&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import operator

class InvalidQuery(Exception):
    pass

class Filter(object):
    binary_operators = {
        u&quot;=&quot;: operator.eq,
        u&quot;==&quot;: operator.eq,
        u&quot;eq&quot;: operator.eq,

        u&quot;&amp;lt;&quot;: operator.lt,
        u&quot;lt&quot;: operator.lt,

        u&quot;&amp;gt;&quot;: operator.gt,
        u&quot;gt&quot;: operator.gt,

        u&quot;&amp;lt;=&quot;: operator.le,
        u&quot;≤&quot;: operator.le,
        u&quot;le&quot;: operator.le,

        u&quot;&amp;gt;=&quot;: operator.ge,
        u&quot;≥&quot;: operator.ge,
        u&quot;ge&quot;: operator.ge,

        u&quot;!=&quot;: operator.ne,
        u&quot;≠&quot;: operator.ne,
        u&quot;ne&quot;: operator.ne,
    }

    multiple_operators = {
        u&quot;or&quot;: any,
        u&quot;∨&quot;: any,
        u&quot;and&quot;: all,
        u&quot;∧&quot;: all,
    }

    def __init__(self, tree):
        self._eval = self.build_evaluator(tree)

    def __call__(self, value):
        return self._eval(value)

    def build_evaluator(self, tree):
        try:
            operator, nodes = list(tree.items())[0]
        except Exception:
            raise InvalidQuery(&quot;Unable to parse tree %s&quot; % tree)
        try:
            op = self.multiple_operators[operator]
        except KeyError:
            try:
                op = self.binary_operators[operator]
            except KeyError:
                raise InvalidQuery(&quot;Unknown operator %s&quot; % operator)
            return lambda value: op(value, nodes)
        # Iterate over every item in the list of the value linked
        # to the logical operator, and compile it down to its own
        # evaluator.
        elements = [self.build_evaluator(node) for node in nodes]
        return lambda value: op((e(value) for e in elements))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To support the &lt;code&gt;and&lt;/code&gt; and &lt;code&gt;or&lt;/code&gt; operators, we leverage the &lt;code&gt;all&lt;/code&gt; and &lt;code&gt;any&lt;/code&gt; built-in Python functions. They are called with an argument that is a generator that evaluates each one of the sub-evaluator, doing the trick.&lt;/p&gt;
&lt;p&gt;Unicode is the new sexy, so I&apos;ve also added Unicode symbols support.&lt;/p&gt;
&lt;p&gt;And it is now possible to implement our full example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; f = Filter(
...     {&quot;∨&quot;: [
...         {&quot;∧&quot;: [
...             {&quot;≥&quot;: 1},
...             {&quot;≤&quot;: 2},
...         ]},
...         {&quot;∧&quot;: [
...             {&quot;≥&quot;: 4},
...             {&quot;≤&quot;: 6},
...         ]},
...     ]})
&amp;gt;&amp;gt;&amp;gt; f(5)
True
&amp;gt;&amp;gt;&amp;gt; f(8)
False
&amp;gt;&amp;gt;&amp;gt; f(1)
True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As an exercise, you could try to add the &lt;code&gt;not&lt;/code&gt; operator, which deserve its own category as it is a unary operator!&lt;/p&gt;
&lt;p&gt;In the next blog post, we will see how to improve that filter with more features, and how to implement a domain-specific language on top of it, to make humans happy when writing the filter!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/05/IMG_20180427_180044--1-.jpg&quot; alt=&quot;Hole and Henni – François Charlier, 2018In this drawing, the artist represents the deepness of functional programming and how its horse power can help you escape many dark situations.&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>Correct HTTP scheme in WSGI with Cloudflare</title><link>https://julien.danjou.info/blog/correct-http-scheme-in-wsgi-with-cloudflare/</link><guid isPermaLink="true">https://julien.danjou.info/blog/correct-http-scheme-in-wsgi-with-cloudflare/</guid><description>I&apos;ve recently been using Cloudflare as an HTTP frontend for some applications, and getting things working correctly with WSGI was unobvious.</description><pubDate>Wed, 25 Apr 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve recently been using &lt;a href=&quot;https://cloudflare.com&quot;&gt;Cloudflare&lt;/a&gt; as an HTTP frontend for some applications, and getting things working correctly with WSGI was unobvious.&lt;/p&gt;
&lt;p&gt;In Python, &lt;a href=&quot;https://en.wikipedia.org/wiki/Web_Server_Gateway_Interface&quot;&gt;WSGI&lt;/a&gt; is the standard protocol to write a Web application. All Web frameworks that I know follows it. And many of those Web frameworks leverage some request environment variables to learn how the request has been made.&lt;/p&gt;
&lt;p&gt;One of those environment variables is &lt;code&gt;wsgi.url_scheme&lt;/code&gt;, and it contains either &lt;code&gt;http&lt;/code&gt; or &lt;code&gt;https&lt;/code&gt;, depending on the protocol that has been used to connect to your WSGI server.&lt;/p&gt;
&lt;p&gt;And that&apos;s where things can get messy. If you enable SSL at Cloudflare in &quot;Flexible&quot; mode, your visitor will connect to your Web site using HTTPS, but Cloudflare will connect to your backend using HTTP. That means that for your application, the traffic will appear to be over HTTP, and not HTTPS: &lt;code&gt;wsgi.url_scheme&lt;/code&gt; will be set to &lt;code&gt;http&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/04/Screen-Shot-2018-04-19-at-22.43.55.png&quot; alt=&quot;Cloudflare SSL setting&quot; /&gt;&lt;/p&gt;
&lt;p&gt;That can lead to several problems with some frameworks. For example, the function &lt;code&gt;url_for&lt;/code&gt; of &lt;a href=&quot;http://flask.pocoo.org/&quot;&gt;Flask&lt;/a&gt; will rely on this variable to generate the scheme part of any URL. In this case, it would, therefore, generate URL starting with &lt;code&gt;http://&lt;/code&gt; whereas your visitors are using &lt;code&gt;https&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The usual workaround is to leverage the &lt;code&gt;X-Forwarded-Proto&lt;/code&gt; that is actually &lt;a href=&quot;https://support.cloudflare.com/hc/en-us/articles/200170986-How-does-Cloudflare-handle-HTTP-Request-headers-&quot;&gt;set by Cloudflare&lt;/a&gt;. In the case where Cloudflare proxies the request to your HTTP host, this will be set to &lt;code&gt;https&lt;/code&gt;. By using the &lt;a href=&quot;http://werkzeug.pocoo.org/docs/contrib/fixers/#werkzeug.contrib.fixers.ProxyFix&quot;&gt;werkzeug.contrib.fixers.ProxyFix&lt;/a&gt; module, the variable &lt;code&gt;wsgi.url_scheme&lt;/code&gt; will be set to what &lt;code&gt;X-Forwarded-Proto&lt;/code&gt; is set.&lt;/p&gt;
&lt;p&gt;That would work fine for any application that is directly behind Cloudflare, or any single HTTP reverse proxy.&lt;/p&gt;
&lt;p&gt;But that does not work as soon as you have multiple reverse proxies. If your application runs on top of &lt;a href=&quot;https://heroku.com&quot;&gt;Heroku&lt;/a&gt; for example, they already provide a reverse proxy and overwrite those headers. That gives the following: &lt;code&gt;Visitor -HTTPS-&amp;gt; Cloudflare -HTTP-&amp;gt; Heroku proxy -HTTP-&amp;gt; Heroku dyno&lt;/code&gt;. Once your dyno is reacher, &lt;code&gt;X-Forwarded-For&lt;/code&gt; will be set to &lt;code&gt;http&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Damn it!&lt;/p&gt;
&lt;p&gt;The proper solution is, therefore, to have all your proxies implement &lt;a href=&quot;https://tools.ietf.org/html/rfc7239&quot;&gt;RFC7239&lt;/a&gt;. This RFC defines a new &lt;code&gt;Forwarded&lt;/code&gt; header that can contain all the hops that have forwarded this request, including all the scheme and IP addresses. Unfortunately, this is not implemented by Cloudflare nor Heroku. Bummer!&lt;/p&gt;
&lt;p&gt;Finally, Cloudflare provides yet another custom header named &lt;code&gt;Cf-Visitor&lt;/code&gt;. It contains a JSON payload with the original HTTP scheme used by the visitor: we can use that to solve our issue. Here&apos;s a WSGI middleware to do that:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class CloudflareProxy(object):
    &quot;&quot;&quot;This middleware sets the proto scheme based on the Cf-Visitor header.&quot;&quot;&quot;

    def __init__(self, app):
        self.app = app

    def __call__(self, environ, start_response):
        cf_visitor = environ.get(&quot;HTTP_CF_VISITOR&quot;)
        if cf_visitor:
            try:
                cf_visitor = json.loads(cf_visitor)
            except ValueError:
                pass
            else:
                proto = cf_visitor.get(&quot;scheme&quot;)
                if proto is not None:
                    environ[&apos;wsgi.url_scheme&apos;] = proto
        return self.app(environ, start_response)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can then use it to encapsulate your WSGI application with &lt;code&gt;app = CloudflareProxy(app)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you&apos;re using JavaScript, I noticed that the &lt;a href=&quot;https://github.com/jshttp/forwarded&quot;&gt;forwarded&lt;/a&gt; library provides that same support for Cloudflare along all the other headers – even RFC7239!&lt;/p&gt;
</content:encoded></item><item><title>Lessons from OpenStack Telemetry: Deflation</title><link>https://julien.danjou.info/blog/lessons-from-openstack-telemetry-deflation/</link><guid isPermaLink="true">https://julien.danjou.info/blog/lessons-from-openstack-telemetry-deflation/</guid><description>This post is the second and final episode of Lessons from OpenStack Telemetry. If you have missed the first post, you can read it here.</description><pubDate>Thu, 19 Apr 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This post is the second and final episode of &lt;em&gt;Lessons from OpenStack Telemetry&lt;/em&gt;. If you have missed the first post, you can read it &lt;a href=&quot;https://julien.danjou.info/blog/lessons-from-openstack-telemetry-incubation&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Splitting&lt;/h2&gt;
&lt;p&gt;At some point, the rules relaxed on new projects addition with the Big Tent initiative, allowing us to rename ourselves to the OpenStack Telemetry team and splitting Ceilometer into several subprojects: Aodh (alarm evaluation functionality) and Panko (events storage). Gnocchi was able to join the OpenStack Telemetry party for its first anniversary.&lt;/p&gt;
&lt;p&gt;Finally being able to split Ceilometer into several independent pieces of software allowed us to tackle technical debt more rapidly. We built autonomous teams for each project and gave them the same liberty they had in Ceilometer. The cost of migrating the code base to several projects was higher than we wanted it to be, but we managed to build a clear migration path nonetheless.&lt;/p&gt;
&lt;h2&gt;Gnocchi Shamble&lt;/h2&gt;
&lt;p&gt;With Gnocchi in town, we stopped all efforts on Ceilometer storage and API and expected people to adopt Gnocchi. What we underestimated is the unwillingness of many operators to think about telemetry. They did not want to deploy anything to have telemetry features in the first place, so adding yet a new component (a timeseries database) to have proper metric features was seen a burden – and sometimes not seen at all.&lt;br /&gt;
Indeed, we also did not communicate enough on our vision for that transition. After two years of existence, many operators were asking what Gnocchi was and what they needed it for. They deployed Ceilometer and its bogus storage and API and were confused about needing yet another piece of software.&lt;/p&gt;
&lt;p&gt;It took us more than two years to deprecate the Ceilometer storage and API, which is way too long.&lt;/p&gt;
&lt;h2&gt;Deflation&lt;/h2&gt;
&lt;p&gt;In the meantime, people were leaving the OpenStack boat. Soon enough, we started to feel the shortage of human resources. Smartly, we never followed the OpenStack trend of imposing blueprints, specs, bug reports or any process to contributors, obeying my list of &lt;a href=&quot;https://julien.danjou.info/blog/foss-projects-management-bad-practice&quot;&gt;open source best practice&lt;/a&gt;. This flexibility allowed us to iterate more rapidly; compared to other OpenStack projects; we were going faster proportionately to the size of our contributor base.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.unsplash.com/photo-1520018319835-74bf61f79844?ixlib=rb-0.3.5&amp;amp;q=80&amp;amp;fm=jpg&amp;amp;crop=entropy&amp;amp;cs=tinysrgb&amp;amp;w=1080&amp;amp;fit=max&amp;amp;ixid=eyJhcHBfaWQiOjExNzczfQ&amp;amp;s=7b410a77641efbb205b4157f7b4c62b0&quot; alt=&quot;Capturer Le moment&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Nonetheless, we felt like bailing out a sinking ship. Our contributors were disappearing while we were swamped with technical debt: half-baked feature, unfinished migration, legacy choices and temporary hacks. After the big party that happened, we had to wash the dishes and sweep the floor.&lt;/p&gt;
&lt;p&gt;Being part of OpenStack started to feel like a burden in many ways. The inertia of OpenStack being a big project was beginning to surface, so we put up a lot of efforts to dodge most of its implications. Consequently, the team was perceived as an outlier, which does not help, especially when you have to interact with a lot your neighbors.&lt;/p&gt;
&lt;p&gt;The OpenStack Foundation never understood the organization of our team. They would refer to us as &quot;Ceilometer&quot; whereas we formally renamed ourselves to &quot;Telemetry&quot; since we were englobing four server projects and a few libraries. For example, while Gnocchi has been an OpenStack project for two years before leaving, it has never been listed on the &lt;a href=&quot;https://www.openstack.org/software/project-navigator/&quot;&gt;project navigator&lt;/a&gt; maintained by the foundation.&lt;/p&gt;
&lt;p&gt;That&apos;s a funny anecdote that demonstrates the peculiarity of our team, and how it has been both a strength and a weakness.&lt;/p&gt;
&lt;h2&gt;Competition&lt;/h2&gt;
&lt;p&gt;Nobody was trying to do what we were doing when we started Ceilometer. We filled the space of metering OpenStack. However, as the number of companies involved increased and the friction with it along, some people grew unhappy. The race to have a seat at the table of the feast and becoming a &lt;em&gt;Project Team Leader&lt;/em&gt; was strong, so some people preferred to create their project rather than trying to play the contribution game. In many areas, including our, that divided the effort up to a ridiculous point where several teams where doing the exact the same thing, or were trying to step on each other toes to kill the competitors.&lt;/p&gt;
&lt;p&gt;We spent a significant amount of time trying to bring other teams in the Telemetry scope, to unify our efforts, without much success. Some companies were not embracing open-source because of their cultural differences, while some others had no interest to join a project where they would not be seen as the leader.&lt;/p&gt;
&lt;p&gt;That fragmentation did not help us, but also did not do much harm in the end. Most of those projects are now either dead or becoming irrelevant as the rest of the world caught up on what they were trying to do.&lt;/p&gt;
&lt;h2&gt;Epilogue&lt;/h2&gt;
&lt;p&gt;As of 2018, I&apos;m the PTL for Telemetry – because nobody else ran. The official list of maintainer for the telemetry projects is five people: two are inactive, and three are part-time. During the latest development cycle (Queens), 48 people committed in Ceilometer, though only three developers made impactful contributions. The code size has been divided by two since the peak: Ceilometer is now 25k lines of code long.&lt;/p&gt;
&lt;p&gt;Panko and Aodh have no active developer. A Red Hat colleague and I are maintaining the projects afloat to keep it working.&lt;/p&gt;
&lt;p&gt;Gnocchi has humbly thriven since it left OpenStack. The stains from having been part of OpenStack are not yet all gone. It has a small community, but users see its real value and enjoy using it.&lt;/p&gt;
&lt;p&gt;Those last six years have been intense, and riding the OpenStack train has been amazing. As I concluded in the first blog post of this series, most of us had a great time overall; the point of those writings is not to complain, but to reflect.&lt;/p&gt;
&lt;p&gt;I find it fascinating to see how the evolution of a piece of software and the metamorphosis of its community are entangled. The amount of politics that a corporately-backed project of this size generates is majestic and has a prominent influence on the outcome of software development.&lt;/p&gt;
&lt;p&gt;So, what&apos;s next? Well, as far as Ceilometer is concerned, we still have ideas and plans to keep shrinking its footprint to a minimum. We hope that one-day Ceilometer will become irrelevant – at least that&apos;s what we&apos;re trying to achieve so we don&apos;t have anything to maintain. That mainly depends on how the myriad of OpenStack projects will chose to address their metering.&lt;/p&gt;
&lt;p&gt;We don&apos;t see any future for Panko nor Aodh.&lt;/p&gt;
&lt;p&gt;Gnocchi, now blooming outside of OpenStack, is still young and promising. We&apos;ve plenty of ideas and every new release brings new fancy features. The storage of timeseries at large scale is exciting. Users are happy, and the ecosystem is growing.&lt;/p&gt;
&lt;p&gt;We&apos;ll see how all of that concludes, but I&apos;m sure it&apos;ll be new lessons to learn and write about in six years!&lt;/p&gt;
</content:encoded></item><item><title>Lessons from OpenStack Telemetry: Incubation</title><link>https://julien.danjou.info/blog/lessons-from-openstack-telemetry-incubation/</link><guid isPermaLink="true">https://julien.danjou.info/blog/lessons-from-openstack-telemetry-incubation/</guid><description>It was mostly around that time in 2012 that I and a couple of fellow open-source enthusiasts started working on Ceilometer, the first piece of software from the OpenStack Telemetry project. Six years</description><pubDate>Thu, 12 Apr 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It was mostly around that time in 2012 that I and a couple of fellow open-source enthusiasts started working on Ceilometer, the first piece of software from the OpenStack Telemetry project. Six years have passed since then. I&apos;ve been thinking about this blog post for several months (even years, maybe), but lacked the time and the hindsight needed to lay out my thoughts properly. In a series of posts, I would like to share my observations about the Ceilometer development history.&lt;/p&gt;
&lt;p&gt;To understand the full picture here, I think it is fair to start with a small retrospective on the project. I&apos;ll try to keep it short, and it will be unmistakably biased, even if I&apos;ll do my best to stay objective – bear with me.&lt;/p&gt;
&lt;h2&gt;Incubation&lt;/h2&gt;
&lt;p&gt;Early 2012, I remember discussing with the first Ceilometer developers the right strategy to solve the problem we were trying to address. The company I worked for wanted to run a public cloud, and billing the resources usage was at the heart of the strategy. The fact that no components in OpenStack were exposing any consumption API was a problem.&lt;/p&gt;
&lt;p&gt;We debated about how to implement those metering features in the cloud platform. There were two natural solutions: either achieving some resource accounting report in each OpenStack projects or building a new software on the side, covering for the lack of those functionalities.&lt;/p&gt;
&lt;p&gt;At that time there were only less than a dozen of OpenStack projects. Still, the burden of patching every project seemed like an infinite task. Having code reviewed and merged in the most significant projects took several weeks, which, considering our timeline, was a show-stopper. We wanted to go fast.&lt;/p&gt;
&lt;p&gt;Pragmatism won, and we started implementing Ceilometer using the features each OpenStack project was offering to help us: very little.&lt;/p&gt;
&lt;p&gt;Our first and obvious candidate for usage retrieval was Nova, where Ceilometer aimed to retrieves statistics about virtual machines instances utilization. Nova offered no API to retrieve those data – and still doesn&apos;t. Since it was out of the equation to wait several months to have such an API exposed, we took the shortcut of polling directly libvirt, Xen or VMware from Ceilometer.&lt;/p&gt;
&lt;p&gt;That&apos;s precisely how temporary hacks become historical design. Implementing this design broke the basis of the abstraction layer that Nova aims to offer.&lt;/p&gt;
&lt;p&gt;As time passed, several leads were followed to mitigate those trade-offs in better ways. But on each development cycle, getting anything merged in OpenStack became harder and harder. It went from patches long to review, to having a long list of requirements to merge anything. Soon, you&apos;d have to create a blueprint to track your work, write a full specification linked to that blueprint, with that specification being reviewed itself by a bunch of the so-called core developers. The specification had to be a thorough document covering every aspect of the work, from the problem that was trying to be solved, to the technical details of the implementation. Once the specification was approved, which could take an entire cycle (6 months), you&apos;d have to make sure that the Nova team would make your blueprint a priority. To make sure it was, you would have to fly a few thousands of kilometers from home to an OpenStack Summit, and orally argue with developers in a room filled with hundreds of other folks about the urgency of your feature compared to other blueprints.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/04/ods_1-tripleo_design_session.jpg&quot; alt=&quot;An OpenStack design session in Hong-Kong, 2013&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Even if you passed all of those ordeals, the code you&apos;d send could be rejected, and you&apos;d get back to updating your specification to shed light on some particular points that confused people. Back to square one.&lt;/p&gt;
&lt;p&gt;Nobody wanted to play that game. Not in the Telemetry team at least.&lt;/p&gt;
&lt;p&gt;So Ceilometer continued to grow, surfing the OpenStack hype curve. More developers were joining the project every cycle – each one with its list of ideas, features or requirements cooked by its in-house product manager.&lt;/p&gt;
&lt;p&gt;But many features did not belong in Ceilometer. They should have been in different projects. Ceilometer was the first OpenStack project to pass through the OpenStack Technical Committee incubation process that existed before the rules were relaxed.&lt;/p&gt;
&lt;p&gt;This incubation process was uncertain, long, and painful. We had to justify the existence of the project, and many technical choices that have been made. Where we were expecting the committee to challenge us at fundamental decisions, such as breaking abstraction layers, it was mostly nit-picking about Web frameworks or database storage.&lt;/p&gt;
&lt;h2&gt;Consequences&lt;/h2&gt;
&lt;p&gt;The rigidity of the process discouraged anyone to start a new project for anything related to telemetry. Therefore, everyone went ahead and started dumping its idea in Ceilometer itself. With more than ten companies interested, the frictions were high, and the project was at some point pulled apart in all directions. This phenomenon was happening to every OpenStack projects &lt;em&gt;anyway&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;On the one hand, many contributions brought marvelous pieces of technology to Ceilometer. We implemented several features you still don&apos;t find any metering system. Dynamically sharded, automatic horizontally scalable polling? Ceilometer has that for years, whereas you can&apos;t have it in, e.g., Prometheus.&lt;/p&gt;
&lt;p&gt;On the other hand, there were tons of crappy features. Half-baked code merged because somebody needed to ship something. As the project grew further, some of us developers started to feel that this was getting out of control and could be disastrous. The technical debt was growing as fast as the project was.&lt;/p&gt;
&lt;p&gt;Several technical choices made were definitely &lt;em&gt;bad&lt;/em&gt;. The architecture was a mess; the messaging bus was easily overloaded, the storage engine was non-performant, etc. People would come to me (as I was the &lt;em&gt;Project Team Leader&lt;/em&gt; at that time) and ask why the REST API would need 20 minutes to reply to an autoscaling request. The willingness to solve everything for everyone was killing Ceilometer. It&apos;s around that time that I decided to step out of my role of PTL and started working on Gnocchi to, at least, solve one of our biggest challenge: efficient data storage.&lt;/p&gt;
&lt;p&gt;Ceilometer was also suffering from the poor quality of many OpenStack projects. As Ceilometer retrieves data from a dozen of other projects, it has to use their interface for data retrieval (API calls, notifications) – or sometimes, palliate for their lack of any interface. Users were complaining about Ceilometer dysfunctioning while the root of the problem was actually on the other side, in the polled project. The polling agent would try to retrieve the list of virtual machines running on Nova, but just listing and retrieving this information required several HTTP requests to Nova. And those basic retrieval requests would overload the Nova API. The API does not offer any genuine interface from where the data could be retrieved in a small number of calls. And it had terrible performances.&lt;br /&gt;
From the point of the view of the users, the load was generated by Ceilometer. Therefore, Ceilometer &lt;strong&gt;was&lt;/strong&gt; the problem. We had to imagine new ways of circumventing tons of limitation from our siblings. That was exhausting.&lt;/p&gt;
&lt;p&gt;At its peak, during the Juno and Kilo releases (early 2015), the code size of Ceilometer reached 54k lines of code, and the number of committers reached 100 individuals (20 regulars). We had close to zero happy user, operators were hating us, and everybody was wondering what the hell was going in those developer minds.&lt;/p&gt;
&lt;p&gt;Nonetheless, despite the impediments, most of us had a great time working on Ceilometer. Nothing&apos;s ever perfect. I&apos;ve learned tons of things during that period, which were actually mostly non-technical. Community management, social interactions, human behavior and politics were at the heart of the adventure, offering a great opportunity for self-improvement.&lt;/p&gt;
&lt;p&gt;In the next blog post, I will cover what happened in the years that followed that booming period, up until today. Stay tuned!&lt;/p&gt;
</content:encoded></item><item><title>Is Python a Good Choice for Entreprise Projects?</title><link>https://julien.danjou.info/blog/is-python-a-good-choice-for-entreprise-projects/</link><guid isPermaLink="true">https://julien.danjou.info/blog/is-python-a-good-choice-for-entreprise-projects/</guid><description>A few weeks ago, one of my followers, Morteza, reached out and asked me the following:  &gt; I develop projects mostly with Python, but I am scared that Python is not a good choice for enterprise project</description><pubDate>Wed, 04 Apr 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few weeks ago, one of my followers, Morteza, reached out and asked me the following:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I develop projects mostly with Python, but I am scared that Python is not a good choice for enterprise projects. In many cases, I&apos;ve encountered a situation where Python performance was not sufficient, like thread spawning and so on, and as you know, the GIL supports one thread at the time.&lt;br /&gt;
Some friends told me to try to use Java, C++ or even Go for enterprise projects instead of Python. I see many job boards that require Python just for testing, QA or some small projects. I feel that Python is a small gun for showing my experiences and that I&apos;d have to choose an alternative language.&lt;br /&gt;
As you are advanced and professional in many topics especially in Python, I&apos;d need your advice. Is Python good enough for enterprise systems? Or should I choose an alternative language which fills the gaps that exist in Python?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you follow me for a long time, you know I&apos;ve been doing Python for more than ten years now and even wrote two books about it. So while I&apos;m obviously biased, and before writing a reply, I would also like to take a step back and reassure you, dear reader, that I&apos;ve used plenty of other programming languages those last 20 years: Perl, C, PHP, Lua, Lisp, Java, etc. I&apos;ve built tiny to big projects with some of them, and I consider that Lisp is the best programming language. 😅 Therefore, I like to think that I&apos;m not overly partial.&lt;/p&gt;
&lt;p&gt;To reply to Morteza, I would say that you first need to acknowledge that a language itself is not slow or fast. English is not faster than French; however, some French people speak faster than English people.&lt;/p&gt;
&lt;p&gt;So then, yes, CPython, the chief implementation of the Python programming language has some limitations: the GIL (&lt;em&gt;Global Interpreter Lock&lt;/em&gt;) as Morteza says, is the most significant parallelism limiter. The rest of the language is being optimized regularly, and you can follow the work done in each Python version to see where this is going. CPython gets faster on each minor version.&lt;/p&gt;
&lt;p&gt;On the other hand, don&apos;t think that Go or Java are miracles: they both have their limitations. For example, you can read this compelling presentation from Ben Bangert at Mozilla entitled &quot;&lt;a href=&quot;https://docs.google.com/presentation/d/1LO_WI3N-3p2Wp9PDWyv5B6EGFZ8XTOTNJ7Hd40WOUHo/edit?pli=1#slide=id.g70b0035b2_1_168&quot;&gt;From Python to Go and back again&lt;/a&gt;&quot;. Ben explains some of the limitations that he encountered while switching to Go.&lt;/p&gt;
&lt;p&gt;I&apos;m sure you can find problems and limitations with the Java Virtual Machine too.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.unsplash.com/photo-1475539175801-4f770d7d1a49?ixlib=rb-0.3.5&amp;amp;q=80&amp;amp;fm=jpg&amp;amp;crop=entropy&amp;amp;cs=tinysrgb&amp;amp;w=1080&amp;amp;fit=max&amp;amp;ixid=eyJhcHBfaWQiOjExNzczfQ&amp;amp;s=08d0de01765dce0d3f464715abc656ce&quot; alt=&quot;Two jockeys riding horses head-to-head during a race&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In &lt;a href=&quot;https://scaling-python.com&quot;&gt;Scaling Python&lt;/a&gt;, I wrote a few chapters covering the GIL and how you can circumvent its limitation. If you write widely scalable applications, the GIL is not such a big deal, as you need, anyway, to spread the load across multiple servers, not only on several processors.&lt;/p&gt;
&lt;p&gt;There are tons of companies running Python applications at large scale, e.g. &lt;a href=&quot;https://thenewstack.io/instagram-makes-smooth-move-python-3/&quot;&gt;Instagram&lt;/a&gt;, &lt;a href=&quot;https://www.python.org/about/quotes/&quot;&gt;Google and YouTube&lt;/a&gt;, &lt;a href=&quot;https://blogs.dropbox.com/tech/?s=python&quot;&gt;Dropbox&lt;/a&gt; or &lt;a href=&quot;https://www.paypal-engineering.com/tag/python/&quot;&gt;PayPal&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Therefore, no, Python is not only for QA applications, no more than Java is only good for browser applets nor Go is for devops or whatever.&lt;/p&gt;
&lt;p&gt;They all are different languages that approach problems from different angles. Depending on your mindset and on the solution that you want to implement, some might appear better equipped than others. Their virtual machines or compilers are marvelous, but also have their limitations and shortcomings that you need to be aware of so you can avoid falling into a big trap.&lt;/p&gt;
&lt;p&gt;Of course, another approach is to remove all those issues by going down a layer and use a lower level language, e.g. C or C++. That&apos;ll remove those limitations for sure: no Python GIL, no Go resources leaking, no JVM startup slowness, etc. However, it&apos;ll add a &lt;em&gt;ton&lt;/em&gt; of extra work and problems that YOU will have to solve – puzzles that are already resolved by higher-level languages. That&apos;s a matter of trade-offs: do you want to write a blazingly fast program in 10 years or do you want to write a decently fast program in 1 year? 😏&lt;/p&gt;
&lt;p&gt;In the end, picking a language is not only a matter of performance but also a concern of support, community, and ecosystem. Picking battle-tested languages like Python and Java is the assurance of reliability and trustworthiness, while selecting a younger language like Rust might be an exciting ride. Doing some &quot;reality check&quot; is always worth considering before choosing a language. If you wanted to write an application that uses, e.g., AMQP and HTTP/2, are you sure that there are libraries providing those features and that are broadly used and supported? Or are you ready to commit time to maintain them yourself?&lt;/p&gt;
&lt;p&gt;Again, Python is pretty solid here. Considering the extensive practice it has, there are tons of generously used libraries for everything you could ever need. The community is large and the ecosystem is flourishing.&lt;/p&gt;
&lt;p&gt;In the end, I do think that yes, Python is a terrific choice for any enterprise projects, and considering the number of existing projects it counts, I&apos;m not the only one thinking that way.&lt;/p&gt;
&lt;p&gt;Feel free to share your experience – or even projects – in the comments section below!&lt;/p&gt;
</content:encoded></item><item><title>Gnocchi engine optimization</title><link>https://julien.danjou.info/blog/gnocchi-engine-optimization/</link><guid isPermaLink="true">https://julien.danjou.info/blog/gnocchi-engine-optimization/</guid><description>Software speed is relative.  After all, it is the result of a set of trade-offs made between the ease of programming and the speed of hardware. The comfort of the developer and its use of multiple abs</description><pubDate>Tue, 27 Mar 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Software speed is relative.&lt;/p&gt;
&lt;p&gt;After all, it is the result of a set of trade-offs made between the ease of programming and the speed of hardware. The comfort of the developer and its use of multiple abstraction layers has a direct impact on decreasing the cost in time (and therefore in money), while it on the other hand increases the hardware expenditure as the software is less performant. In the end, whichever between optimization and hardware that is the cheapest gets privileged.&lt;/p&gt;
&lt;p&gt;Of course, there are terrible exceptions, such as picking the wrong algorithm or including &lt;code&gt;sleep()&lt;/code&gt; calls, but the essence of it is here. Pick C to be fast, saving money on hardware and spending it on development, or pick Java to save money on development, while making rich hardware manufacturers.&lt;/p&gt;
&lt;p&gt;Last month, a co-worker at &lt;a href=&quot;https://redhat.com&quot;&gt;Red Hat&lt;/a&gt; picked Gnocchi for a test run and was disappointed by the performance he saw for his particular usage. After correctly understanding the use case scenario, I wrote a small test case that implemented this scheme and popped out my favorite code profiler. You know how I roll.&lt;/p&gt;
&lt;p&gt;The profiling result made the performance issue obvious. Along with its releases, Gnocchi evolved from a one metric at a time processing approach to a bunch of metric at a time approach – especially since Gnocchi 4 and the introduction of the &lt;em&gt;sacks&lt;/em&gt;. However, that batched approach is not yet complete in Gnocchi 4.2, and the processing engine still manipulates metrics one by one in parallel. The parallelization using processes and threads makes sure that the CPU usage is high and that the I/O latency does not impact the processing too much.&lt;/p&gt;
&lt;p&gt;Processing incoming measures can be therefore schematized as this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-engine-4-1.png&quot; alt=&quot;gnocchi-engine-4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In the schema above, each operation in red is an I/O operation. The three branches I drew represents three metrics being processed. Obviously, if there were ten metrics, there would be ten branches, creating even more I/O operations. With the current Gnocchi 4.2 code, the number of I/O operations for processing a sack of metric can be roughly computed to &lt;code&gt;2 + (5 × M × D × G)&lt;/code&gt; actions, with &lt;code&gt;M&lt;/code&gt; the number of metrics and &lt;code&gt;D&lt;/code&gt; the number of definitions in the archive policy and &lt;code&gt;G&lt;/code&gt; the number of aggregation methods. For my test scenario, I used &lt;code&gt;D=1&lt;/code&gt; and &lt;code&gt;G=1&lt;/code&gt;, which is what can be seen on the diagram above.&lt;/p&gt;
&lt;p&gt;The obvious solution is to merge those I/O operations for each metric in a single I/O operations for a bunch of metrics. This allows for storage backends to batch the reading and writing operations, reducing latency and improving throughput.&lt;/p&gt;
&lt;p&gt;It took me a few tens of patches and a few code reviews from my peers to rework the internal storage engine of Gnocchi. The new engine is now ready to be used and merged into the &lt;em&gt;master&lt;/em&gt; branch.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-engine-5.png&quot; alt=&quot;gnocchi-engine-5&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The new engine reduces the number of I/O operations to process a bunch of metric to &lt;code&gt;5 + M&lt;/code&gt; – a (at least) five times reduction in the amount of operations. In my case, for 1000 metrics being processed in a batch, with only one aggregation, that decreases the quantity of transactions from 5002 to 1005.&lt;/p&gt;
&lt;p&gt;A typical metric has 6 aggregation methods defined usually, so that could reduce the number of I/O operations from 40,002 to only 1005 for 1000 metrics – a fourty times reduction of I/O operations. The benchmark code that I wrote, which implements the desired use case with a single aggregation, is now performing more than four times faster.&lt;/p&gt;
&lt;p&gt;Not all the drivers will benefit from this improvement, as some of them are better at doing batched operations than others; Redis is great at it, while Swift is not. And even if the number I/O operations has been largely reduced, they still need to be fully executed, which can take time depending on the backend performance. It&apos;s a really great improvement, not a silver bullet.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://sileht.net&quot;&gt;Mehdi&lt;/a&gt; started to use that new internal driver API to implement a &lt;a href=&quot;http://rocksdb.org/&quot;&gt;RocksDB&lt;/a&gt; driver. While it has its own limitation (has to be single-threaded) that we will need to circumvent, it could improve performance for the non-distributed use-case by a large magnitude.&lt;/p&gt;
&lt;p&gt;This code will be included in the next Gnocchi major release in a few weeks, so stay tuned for further update. And benchmark, I hope!&lt;/p&gt;
</content:encoded></item><item><title>On blog migration</title><link>https://julien.danjou.info/blog/blog-migration-ghost/</link><guid isPermaLink="true">https://julien.danjou.info/blog/blog-migration-ghost/</guid><description>I&apos;ve started my first Web page in 1998 and one could say that it evolved quite a bit in the meantime. From a Frontpage designed Web site with frames, it evolved to plain HTML files. I&apos;ve started blogg</description><pubDate>Wed, 21 Mar 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve started my first Web page in 1998 and one could say that it evolved quite a bit in the meantime. From a Frontpage designed Web site with frames, it evolved to plain HTML files. I&apos;ve started blogging in 2003, though the archives of this blog only gets back to 2007. Truth is, many things I wrote in the first years were short (there were no Twitter) and not that relevant nowadays. Therefore, I never migrated them along the road of the many migrations that site had.&lt;/p&gt;
&lt;p&gt;The last time I switched this site engine was in 2011, were I switched from &lt;a href=&quot;https://www.gnu.org/software/emacs-muse/index.html&quot;&gt;Emacs Muse&lt;/a&gt; (and my custom &lt;em&gt;muse-blog.el&lt;/em&gt; extension) to &lt;a href=&quot;https://github.com/hyde/hyde&quot;&gt;Hyde&lt;/a&gt;, a static Web site generator written in Python.&lt;/p&gt;
&lt;p&gt;That taught me a few things.&lt;/p&gt;
&lt;p&gt;First, you can&apos;t really know for sure which project will be a ghost in 5 years. I had no clue back then that Hyde author would lose interest and struggle passing the maintainership to someone else. The community was not big but it existed. Betting on a horse is part skill and part chance. My skills were probably lower seven years ago and I also may have had bad luck.&lt;/p&gt;
&lt;p&gt;Secondly, maintaining a Web site is painful. I used to blog more regularly a few years ago, as the friction of using a dynamic blog engine was lower than spawning my deprecated static engine. Knowing that it needs 2 minutes to generate a static Web site really makes it difficult to compose and see the result at the same time without losing patience. It took me a few years to decide it was time to invest in the migration. I just jumped from Hyde to &lt;a href=&quot;https://ghost.org/&quot;&gt;Ghost&lt;/a&gt;, hosted on their Pro engine as I don&apos;t want to do any maintenance. Let&apos;s be honest, I&apos;ve no will to inflict myself the maintenance of a JavaScript blogging engine.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://images.unsplash.com/photo-1486262715619-67b85e0b08d3?ixlib=rb-0.3.5&amp;amp;q=80&amp;amp;fm=jpg&amp;amp;crop=entropy&amp;amp;cs=tinysrgb&amp;amp;w=1080&amp;amp;fit=max&amp;amp;ixid=eyJhcHBfaWQiOjExNzczfQ&amp;amp;s=b279db1ffb6919c7cc32df9b5300cdc7&quot; alt=&quot;Macro of motor engine with gears and screws&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The positive side is that this is still Markdown based, so the migration job was not so painful. Ghost offers a &lt;a href=&quot;https://api.ghost.org/&quot;&gt;REST API&lt;/a&gt; which allow to manipulate most of the content. It works fine, and I was able to leverage the &lt;a href=&quot;https://github.com/rycus86/ghost-client&quot;&gt;Python ghost-client&lt;/a&gt; to write a tiny migration script to migrate every post.&lt;/p&gt;
&lt;p&gt;I am looking forward to share most of the things that I work on during the next months. I really enjoyed reading contents of great hackers those last years, and I&apos;ve learned ton of things by reading the adventure of smarter engineers.&lt;/p&gt;
&lt;p&gt;It might be my time to share.&lt;/p&gt;
</content:encoded></item><item><title>Scaling a polling Python application with tooz</title><link>https://julien.danjou.info/blog/scaling-a-python-application-tooz/</link><guid isPermaLink="true">https://julien.danjou.info/blog/scaling-a-python-application-tooz/</guid><description>This article is the final one of the series I wrote about scaling a large number of connections in a Python application. If you don&apos;t remember what the problem we&apos;re trying to solve is, here it is, co</description><pubDate>Mon, 05 Mar 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This article is the final one of the series I wrote about scaling a large number of connections in a Python application. If you don&apos;t remember what the problem we&apos;re trying to solve is, here it is, coming from one of my followers:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It so happened that I&apos;m currently working on scaling some Python app. Specifically, now I&apos;m trying to figure out the best way to scale SSH connections - when one server has to connect to thousands (or even tens of thousands) of remote machines in a short period of time (say, several minutes).&lt;br /&gt;
How would you write an application that does that in a scalable way?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The &lt;a href=&quot;https://julien.danjou.info/blog/scaling-python-application-threads&quot;&gt;first blog post&lt;/a&gt; was exploring a solution based on threads, while the &lt;a href=&quot;https://julien.danjou.info/blog/scaling-python-application-asyncio&quot;&gt;second blog post&lt;/a&gt; was exploring an architecture around &lt;em&gt;asyncio&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;In the two first articles, we wrote programs that could handle this problem by using multiple &lt;em&gt;threads&lt;/em&gt; or &lt;em&gt;asyncio&lt;/em&gt; – or both. While this worked pretty well, this had some limitations, such as only using one computer. So this time, we&apos;re going to take a different approach and use multiple computers!&lt;/p&gt;
&lt;h3&gt;The job&lt;/h3&gt;
&lt;p&gt;As we&apos;ve already seen, writing a Python application that connects to a host by ssh can be done using &lt;a href=&quot;http://docs.paramiko.org/en/&quot;&gt;Paramiko&lt;/a&gt; or &lt;a href=&quot;https://github.com/ronf/asyncssh&quot;&gt;asyncssh&lt;/a&gt; as we&apos;ve seen previously. Here again, that will not be the focus of this blog post since it is pretty straightforward to do.&lt;/p&gt;
&lt;p&gt;To keep this exercise simple, we&apos;ll reuse our &lt;code&gt;ping&lt;/code&gt; function from the first article. It looked like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import subprocess

def ping(hostname):
    p = subprocess.Popen([&quot;ping&quot;, &quot;-c&quot;, &quot;3&quot;, &quot;-w&quot;, &quot;1&quot;, hostname],
                         stdout=subprocess.DEVNULL,
                         stderr=subprocess.DEVNULL)
    return p.wait() == 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As a reminder, running this program alone and pinging serially 255 IP addresses takes more than 10 minutes. Let&apos;s try to make it faster by running it in parallel.&lt;/p&gt;
&lt;h3&gt;The architecture&lt;/h3&gt;
&lt;p&gt;Remember: if pinging 255 hosts takes 10 minutes, pinging the whole Internet is going to take forever – around five years at this rate.&lt;/p&gt;
&lt;p&gt;With our ping experiment, we already divided our mission (e.g. &quot;who&apos;s alive on the Internet&quot;) into very small tasks (&quot;ping&quot;). If we want to ping 4 billion hosts, we need to run those tasks in parallel. But one computer is not going to be enough: we need to distribute those tasks to different hosts, so we can use some massive parallelism to go even faster!&lt;/p&gt;
&lt;p&gt;There are two ways to distribute such a set of tasks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Use a queue. That works well for jobs that are not determined in advance, such as user-submitted tasks or that are going to be executed only once.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use a distribution algorithm. That works only for tasks are determined in advance, and that are scheduled regularly, such as polling.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We are going to pick the second option here, as those ping tasks (or polling in the original problem) should regularly be run. That approach will allow us to spread the jobs onto several processes whose can be even spread onto several nodes over a network. We also won&apos;t have to &quot;maintain&quot; the queue (e.g. make it work and monitor it) so that&apos;s also a bonus point.&lt;/p&gt;
&lt;p&gt;That&apos;s infinite horizontal scalability!&lt;/p&gt;
&lt;h3&gt;The distribution algorithm&lt;/h3&gt;
&lt;p&gt;The algorithm we&apos;re going to use to distribute this task is based on a &lt;a href=&quot;https://en.wikipedia.org/wiki/Consistent_hashing&quot;&gt;consistent hashring&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here&apos;s how it works in short. Picture a circular ring. We map objects onto this ring. The ring is then split into partitions. Those partitions are distributed among all the workers. The workers take care of jobs that are in the partitions they are responsible for.&lt;/p&gt;
&lt;p&gt;In the case where a new node joins the ring, it is inserted between 2 nodes and take a bit of their workload. In the case where a node leaves the ring, the partitions it was taking care of are reassigned to its adjacent nodes.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/consistent-hashing.png&quot; alt=&quot;Diagram of consistent hashing ring with partitions distributed among worker nodes&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you want more details, it exists plenty of explanations about how this algorithm work. Feel free to look online!&lt;/p&gt;
&lt;p&gt;However, to make this work, we need to know which nodes are alive or dead. This is another problem to solve, and the best way to tackle it is to use a coordination mechanism. There are plenty of those, from &lt;a href=&quot;https://zookeeper.apache.org/&quot;&gt;Apache ZooKeeper&lt;/a&gt; to &lt;a href=&quot;https://coreos.com/etcd/&quot;&gt;etcd&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Without going too much into details, those pieces of software provide a network service where every node can connect to and can manage its state. If a client gets disconnected or crashes, it&apos;s then easy to consider it as removed. That enables the application to get the full list of nodes, and split the ring accordingly. There&apos;s no need to have any shared state between the nodes other than who&apos;s alive and running.&lt;/p&gt;
&lt;h3&gt;Using group membership&lt;/h3&gt;
&lt;p&gt;To get a list of nodes that are available to help us pinging the Internet, we need a service that provides this and a library to interact with it. Since the use case is pretty simple and I don&apos;t know which backends you like the most, we&apos;re going to use the &lt;a href=&quot;https://pypi.python.org/pypi/tooz&quot;&gt;Tooz&lt;/a&gt; library.&lt;/p&gt;
&lt;p&gt;Tooz provides a coordination mechanism on top of a large variety of backends: ZooKeeper or etcd, as suggested earlier, but also &lt;a href=&quot;https://redis.io&quot;&gt;Redis&lt;/a&gt; or &lt;a href=&quot;https://memcached.org&quot;&gt;memcached&lt;/a&gt; for those who want to live more dangerously. Indeed, while ZooKeeper or etcd can be set up in a synchronized cluster, memcached, on the other hand, is a &lt;a href=&quot;https://en.wikipedia.org/wiki/Single_point_of_failure&quot;&gt;SPOF&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For the sake of the exercise, we&apos;re going to use a single instance of etcd here. Thanks to Tooz, switching to another backend would be a one-line change anyway.&lt;/p&gt;
&lt;p&gt;Tooz provides a &lt;code&gt;tooz.coordination.Coordinator&lt;/code&gt; object that represents the connection to the coordination subsystem. It then exposes an API based on groups and members. A member is a node connected through a &lt;code&gt;Coordinator&lt;/code&gt; instance. A group is a place that members can join or leave.&lt;/p&gt;
&lt;p&gt;Here&apos;s a first implementation of a member joining a group and printing the member list:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import sys
import time

from tooz import coordination

## Check that a client and group ids are passed as arguments
if len(sys.argv) != 3:
    print(&quot;Usage: %s &amp;lt;client id&amp;gt; &amp;lt;group id&amp;gt;&quot; % sys.argv[0])
    sys.exit(1)

## Get the Coordinator object
c = coordination.get_coordinator(
    &quot;etcd3://localhost&quot;,
    sys.argv[1].encode())
## Start it (initiate connection).
c.start(start_heart=True)

group = sys.argv[2].encode()

## Create the group
try:
    c.create_group(group).get()
except coordination.GroupAlreadyExist:
    pass

## Join the group
c.join_group(group).get()

try:
    while True:
        # Print the members list
        members = c.get_members(group)
        print(members.get())
        time.sleep(1)
finally:
    # Leave the group
    c.leave_group(group).get()

    # Stop when we&apos;re done
    c.stop()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Don&apos;t forget to run etcd on your machine before running this program. Running a first instance of this program will print &lt;code&gt;set([&apos;client1&apos;])&lt;/code&gt; every second. As soon as you run a second instance of this program, they both start to print &lt;code&gt;set([&apos;client1&apos;, &apos;client2&apos;])&lt;/code&gt;. If you shut down one of the clients, they will print the member list with only one member of it.&lt;/p&gt;
&lt;p&gt;This can work with any number of client. If a client crashes rather than disconnect properly, its membership will automatically expire a few seconds – you can configure this expiration period with by passing a &lt;code&gt;timeout&lt;/code&gt; value in&lt;br /&gt;
Tooz URL.&lt;/p&gt;
&lt;h3&gt;Using consistent hashing&lt;/h3&gt;
&lt;p&gt;Now that we have a group, which will turn out to be our &lt;em&gt;ring&lt;/em&gt;, we can&lt;br /&gt;
implement consistent hashring on top of it. Fortunately, Tooz also provides an implementation of this that is ready to be used. Rather than using the&lt;br /&gt;
&lt;code&gt;join_group&lt;/code&gt; method, we&apos;re gonna use the &lt;code&gt;join_partitioned_group&lt;/code&gt; method.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import sys
import time

from tooz import coordination

## Check that a client and group ids are passed as arguments
if len(sys.argv) != 3:
    print(&quot;Usage: %s &amp;lt;client id&amp;gt; &amp;lt;group id&amp;gt;&quot; % sys.argv[0])
    sys.exit(1)

## Get the Coordinator object
c = coordination.get_coordinator(
    &quot;etcd3://localhost&quot;,
    sys.argv[1].encode())
## Start it (initiate connection).
c.start(start_heart=True)

group = sys.argv[2].encode()

## Join the partitioned group
p = c.join_partitioned_group(group)

try:
    while True:
        print(p.members_for_object(&quot;foobar&quot;))
        time.sleep(1)
finally:
    # Leave the group
    c.leave_group(group).get()

    # Stop when we&apos;re done
    c.stop()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running this program on one node (or just one terminal) will output the following every second:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python distribution.py client1 foobar
0 handled by set([&apos;client1&apos;])
1 handled by set([&apos;client1&apos;])
2 handled by set([&apos;client1&apos;])
3 handled by set([&apos;client1&apos;])
4 handled by set([&apos;client1&apos;])
5 handled by set([&apos;client1&apos;])
6 handled by set([&apos;client1&apos;])
7 handled by set([&apos;client1&apos;])
8 handled by set([&apos;client1&apos;])
9 handled by set([&apos;client1&apos;])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As soon as a second members join (just run another copy of the script in another terminal), the output changes and both the running programs output the same thing:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0 handled by set([&apos;client2&apos;])
1 handled by set([&apos;client1&apos;])
2 handled by set([&apos;client1&apos;])
3 handled by set([&apos;client1&apos;])
4 handled by set([&apos;client1&apos;])
5 handled by set([&apos;client2&apos;])
6 handled by set([&apos;client2&apos;])
7 handled by set([&apos;client1&apos;])
8 handled by set([&apos;client1&apos;])
9 handled by set([&apos;client2&apos;])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;They just shared the ten objects between them. They &lt;strong&gt;did not communicate with each other&lt;/strong&gt;. They just know each other presence, and since they are using the same algorithm to compute where an object should belong, they share the same&lt;br /&gt;
results. You can do the test with a third copy of the program:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0 handled by set([&apos;client2&apos;])
1 handled by set([&apos;client1&apos;])
2 handled by set([&apos;client1&apos;])
3 handled by set([&apos;client1&apos;])
4 handled by set([&apos;client1&apos;])
5 handled by set([&apos;client2&apos;])
6 handled by set([&apos;client2&apos;])
7 handled by set([&apos;client3&apos;])
8 handled by set([&apos;client1&apos;])
9 handled by set([&apos;client3&apos;])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we got a third client in the mix, excellent! If we stop one of the clients, the rebalancing is done automatically.&lt;/p&gt;
&lt;p&gt;While the consistent hashing approach is great, is has a few characteristics you might want to know about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The distribution algorithm is not made to be perfectly even. If you have a vast number of objects, it might seem pretty even statistically, but if you are trying to distribute two objects on two nodes, it&apos;s probable one node will handle the two objects and the other one none.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The distribution is not done in real time, meaning there&apos;s a small chance that an object might be owned by two nodes at the same time. This is not a problem in a scenario such as this one, since pinging a host twice is not going to be a big deal, but if your job needed to be unique and executed once and only once, this might not be an adequate method of distribution. Rather use a queue which has the proper characteristics.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Distributed ping&lt;/h3&gt;
&lt;p&gt;Now that we have our hashring ready to distribute our job, we can implement our final program!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import sys
import subprocess
import time

from tooz import coordination

## Check that a client and group ids are passed as arguments
if len(sys.argv) != 3:
    print(&quot;Usage: %s &amp;lt;client id&amp;gt; &amp;lt;group id&amp;gt;&quot; % sys.argv[0])
    sys.exit(1)

## Get the Coordinator object
c = coordination.get_coordinator(
    &quot;etcd3://localhost&quot;,
    sys.argv[1].encode())
## Start it (initiate connection).
c.start(start_heart=True)

group = sys.argv[2].encode()

## Join the partitioned group
p = c.join_partitioned_group(group)

class Host(object):
    def __init__(self, hostname):
        self.hostname = hostname

    def __tooz_hash__(self):
        &quot;&quot;&quot;Returns a unique byte identifier so Tooz can distribute this object.&quot;&quot;&quot;
        return self.hostname.encode()

    def __str__(self):
        return &quot;&amp;lt;%s: %s&amp;gt;&quot; % (self.__class__.__name__, self.hostname)

    def ping(self):
        p = subprocess.Popen([&quot;ping&quot;, &quot;-q&quot;, &quot;-c&quot;, &quot;3&quot;, &quot;-W&quot;, &quot;1&quot;,
                              self.hostname],
                             stdout=subprocess.DEVNULL,
                             stderr=subprocess.DEVNULL)
        return p.wait() == 0

hosts_to_ping = [Host(&quot;192.168.2.%d&quot; % i) for i in range(255)]

try:
    while True:
        for host in hosts_to_ping:
            c.run_watchers()
            if p.belongs_to_self(host):
                print(&quot;Pinging %s&quot; % host)
                if host.ping():
                    print(&quot;  %s is alive&quot; % host)
        time.sleep(1)
finally:
    # Leave the group
    c.leave_group(group).get()

    # Stop when we&apos;re done
    c.stop()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the first client starts, it starts iterating on the host, and since it is alone, all hosts belong to it. So it starts pinging all nodes:&lt;/p&gt;
&lt;p&gt;{% syntax %}&lt;br /&gt;
$ python3 ping.py client1 ping&lt;br /&gt;
Pinging &amp;lt;Host: 192.168.2.0&amp;gt;&lt;br /&gt;
&amp;lt;Host: 192.168.2.0&amp;gt; is alive&lt;br /&gt;
Pinging &amp;lt;Host: 192.168.2.1&amp;gt;&lt;br /&gt;
&amp;lt;Host: 192.168.2.1&amp;gt; is alive&lt;br /&gt;
Pinging &amp;lt;Host: 192.168.2.2&amp;gt;&lt;br /&gt;
{% endsyntax %}&lt;/p&gt;
&lt;p&gt;Then, a second client starts pinging too, and automatically the jobs are split. The &lt;code&gt;client1&lt;/code&gt; instance starts skipping some nodes that now belongs to &lt;code&gt;client2&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## client1 output
Pinging &amp;lt;Host: 192.168.2.8&amp;gt;
  &amp;lt;Host: 192.168.2.8&amp;gt; is alive
Pinging &amp;lt;Host: 192.168.2.9&amp;gt;
Pinging &amp;lt;Host: 192.168.2.11&amp;gt;
Pinging &amp;lt;Host: 192.168.2.12&amp;gt;

## client2 output
Pinging &amp;lt;Host: 192.168.2.7&amp;gt;
Pinging &amp;lt;Host: 192.168.2.10&amp;gt;
Pinging &amp;lt;Host: 192.168.2.13&amp;gt;
  &amp;lt;Host: 192.168.2.13&amp;gt; is alive
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On the other hand, &lt;code&gt;client2&lt;/code&gt; is skipping nodes that are belonging to &lt;code&gt;client1&lt;/code&gt;. If you want to scale further our application, we can start new clients on other nodes on the network and expand our pinging system!&lt;/p&gt;
&lt;h3&gt;Just a first step&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://scaling-python.com&quot;&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/the-hacker-guide-to-scaling-python.png&quot; alt=&quot;Cover of The Hacker&apos;s Guide to Scaling Python&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This &lt;code&gt;ping&lt;/code&gt; job does not use a lot of CPU time or I/O bandwidth, neither would the original ssh case by Alon. However, if that would be the case, this method would be even more efficient as the scalability of the resources would be a key.&lt;/p&gt;
&lt;p&gt;These are just the first steps of the distribution and scalability mechanism&lt;br /&gt;
that you can implement using Python. There are a few other options available on top of this mechanism such as defining different weights for different nodes or using replicas to achieve high-availability scenario. I&apos;ve covered those in my book &lt;a href=&quot;https://scaling-python.com&quot;&gt;Scaling Python&lt;/a&gt;, if you&apos;re interested in learning more!&lt;/p&gt;
</content:encoded></item><item><title>Scaling a polling Python application with asyncio</title><link>https://julien.danjou.info/blog/scaling-python-application-asyncio/</link><guid isPermaLink="true">https://julien.danjou.info/blog/scaling-python-application-asyncio/</guid><description>This article is a follow-up of my previous blog post about scaling a large number of connections . If you don&apos;t remember, I was trying to solve one of my followers&apos; problem:  &gt; It so happened that I&apos;m</description><pubDate>Mon, 12 Feb 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This article is a follow-up of my &lt;a href=&quot;https://julien.danjou.info/blog/scaling-python-application-threads&quot;&gt;previous blog post about scaling a large number of connections&lt;/a&gt;. If you don&apos;t remember, I was trying to solve one of my followers&apos; problem:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It so happened that I&apos;m currently working on scaling some Python app. Specifically, now I&apos;m trying to figure out the best way to scale SSH connections - when one server has to connect to thousands (or even tens of thousands) of remote machines in a short period of time (say, several minutes).&lt;br /&gt;
How would you write an application that does that in a scalable way?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In the first article, we wrote a program that could handle large scale of this problem by using multiple &lt;em&gt;threads&lt;/em&gt;. While this worked pretty well, this had some severe limitations. This time, we&apos;re going to take a different approach.&lt;/p&gt;
&lt;h2&gt;The job&lt;/h2&gt;
&lt;p&gt;The job has not changed and is still about connecting to a remote server via&lt;br /&gt;
ssh. This time, rather than faking it by using &lt;em&gt;ping&lt;/em&gt; instead, we are going to connect for real to an ssh server. Once connected to the remote server, the mission will be to run a single command. For the sake of this example, the command that will be run here is just a simple &quot;echo hello world&quot;.&lt;/p&gt;
&lt;h2&gt;Using an event loop&lt;/h2&gt;
&lt;p&gt;This time, rather than leveraging threads, we are using &lt;a href=&quot;https://docs.python.org/3/library/asyncio.html&quot;&gt;asyncio&lt;/a&gt;. &lt;em&gt;Asyncio&lt;/em&gt; is the leading Python event loop system implementation. It allows executing multiple functions (named &lt;em&gt;coroutines&lt;/em&gt;) concurrently. The idea is that each time a coroutine performs an I/O operation, it yields back the control to the event loop. As the input or output might be blocking (e.g., the socket has no data yet to be read), the event loop will reschedule the coroutine as soon as there is work to do. In the meantime, the loop can schedule another coroutine that has something to do – or wait for that to happen.&lt;/p&gt;
&lt;p&gt;Not all libraries are compatible with the &lt;em&gt;asyncio&lt;/em&gt; framework. In our case, we need an ssh library that has support for &lt;em&gt;asyncio&lt;/em&gt;. It happens that &lt;a href=&quot;https://github.com/ronf/asyncssh&quot;&gt;&lt;em&gt;AsyncSSH&lt;/em&gt;&lt;/a&gt; is a Python library that provides ssh connection handling support for asyncio. It is particularly easy to use, and the &lt;a href=&quot;http://asyncssh.readthedocs.io/&quot;&gt;documentation has plenty of examples&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here&apos;s the function that we&apos;re going to use to execute our command on a remote host:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import asyncssh

async def run_command(host, command):
    async with asyncssh.connect(host) as conn:
        result = await conn.run(command)
        return result.stdout
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The function &lt;code&gt;run_command&lt;/code&gt; runs a &lt;code&gt;command&lt;/code&gt; on a remote &lt;code&gt;host&lt;/code&gt; once connected&lt;br /&gt;
to it via ssh. It then returns the standard output of the command. The function uses the keywords &lt;code&gt;async&lt;/code&gt; and &lt;code&gt;await&lt;/code&gt; that are specific to Python &amp;gt;= 3.6 and &lt;em&gt;asyncio&lt;/em&gt;. It indicates that the called functions are coroutine that might be blocking, and that the control is yield back to the event loop.&lt;/p&gt;
&lt;p&gt;As I don&apos;t own hundreds of servers where I can connect to, I will be using a single remote server as the target – but the program will connect to it multiple times. The server is at a latency of about 6 ms, so that&apos;ll magnify a bit the results.&lt;/p&gt;
&lt;p&gt;The first version of this program is simple and stupid. It&apos;ll run N times the &lt;code&gt;run_command&lt;/code&gt; function serially by providing the tasks one at a time to the &lt;em&gt;asyncio&lt;/em&gt; event loop:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;loop = asyncio.get_event_loop()

outputs = [
    loop.run_until_complete(
        run_command(&quot;myserver&quot;, &quot;echo hello world %d&quot; % i))
    for i in range(200)
]
print(outputs)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once executed, the program prints the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ time python3 asyncssh-test.py
[&apos;hello world 0\n&apos;, &apos;hello world 1\n&apos;, &apos;hello world 2\n&apos;, … &apos;hello world 199\n&apos;]
python3 asyncssh-test.py  6.11s user 0.35s system 15% cpu 41.249 total
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It took 41 seconds to connect 200 times to the remote server and execute a simple printing command.&lt;/p&gt;
&lt;p&gt;To make this faster, we&apos;re going to schedule all the coroutines at the same time. We just need to feed the event loop with the 200 coroutines at once. That will give it the ability to schedule them efficiently.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;outputs = loop.run_until_complete(asyncio.gather(
    *[run_command(&quot;myserver&quot;, &quot;echo hello world %d&quot; % i)
      for i in range(200)]))
print(outputs)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By using &lt;code&gt;asyncio.gather&lt;/code&gt;, it is possible to pass a list of coroutines and wait for all of them to be finished. Once run, this program prints the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ time python3 asyncssh-test.py
[&apos;hello world 0\n&apos;, &apos;hello world 1\n&apos;, &apos;hello world 2\n&apos;, … &apos;hello world 199\n&apos;]
python3 asyncssh-test.py  4.90s user 0.34s system 35% cpu 14.761 total
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This version only took ⅓ of the original execution time to finish! As a fun note, the main limitation here is that my remote server is having trouble to handle more than 150 connections in parallel, so this program is a bit tough for it alone.&lt;/p&gt;
&lt;h2&gt;Scalability&lt;/h2&gt;
&lt;p&gt;To show how great this method is, I&apos;ve built a chart below that shows the difference of execution time between the two approaches, depending on the number of hosts the application has to connect to.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/chart-asyncssh.png&quot; alt=&quot;chart-asyncssh&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The trend lines highlight the difference of execution time and how important the concurrency is here. For 10,000 nodes, the time needed for a serial execution would be around 40 minutes whereas it would be only 7 minutes with a cooperative approach – quite a difference. The concurrent approach allows executing one command 205 times a day rather than only 36 times!&lt;/p&gt;
&lt;h2&gt;That was the second step&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://scaling-python.com&quot;&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/the-hacker-guide-to-scaling-python.png&quot; alt=&quot;Cover of The Hacker&apos;s Guide to Scaling Python&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Using an event loop for tasks that can run concurrently due to their I/O intensive nature is really a great way to maximize the throughput of a program. This simple changes made the program &lt;em&gt;6× faster&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Anyhow, this is not the only way to scale Python program. There are a few other options available on top of this mechanism – I&apos;ve covered those in my book &lt;a href=&quot;https://scaling-python.com&quot;&gt;Scaling Python&lt;/a&gt;, if you&apos;re interested in learning more!&lt;/p&gt;
&lt;p&gt;Until then, stay tuned for the next article of this series!&lt;/p&gt;
</content:encoded></item><item><title>Gnocchi 4.2 release</title><link>https://julien.danjou.info/blog/gnocchi-4-2-release/</link><guid isPermaLink="true">https://julien.danjou.info/blog/gnocchi-4-2-release/</guid><description>The time of the release arrived. A little more than three months have passed since the latest minor version, 4.1, has been released. There are tons of improvement and a few nice significant features i</description><pubDate>Tue, 06 Feb 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The time of the release arrived. A little more than three months have passed since the latest minor version, 4.1, has been released. There are tons of improvement and a few nice significant features in this release!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-logo.png&quot; alt=&quot;Gnocchi logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Most of the principal changes are recorded in &lt;a href=&quot;http://gnocchi.xyz/releasenotes/4.2.html&quot;&gt;the 4.2 release notes&lt;/a&gt;, but here are as a few that I find particularly interesting. There were 141 commits since 4.1.0 that we merged. As a comparison, it is a lot less than the 228 we had between 4.0.0 and 4.1.0 or the 375 we had between 3.1.0 and 4.0.0!&lt;/p&gt;
&lt;p&gt;We added two compatibility endpoints on the REST API for &lt;a href=&quot;http://influxdb.org&quot;&gt;InfluxDB&lt;/a&gt; and &lt;a href=&quot;http://prometheus.io&quot;&gt;Prometheus&lt;/a&gt;. We want users coming from those other database systems or using tools that are compatible with them to be able also to use Gnocchi. This is now possible as Gnocchi offers endpoint to write data using the InfluxDB line protocol and Prometheus HTTP API. Reading data using their API is not supported yet though. For example, this has been tested with &lt;a href=&quot;https://www.influxdata.com/time-series-platform/telegraf/&quot;&gt;Telegraf&lt;/a&gt; and works perfectly fine!&lt;/p&gt;
&lt;p&gt;Some other improvements were made, such as enhancing the ACL filtering when using &lt;a href=&quot;https://docs.openstack.org/keystone/latest/&quot;&gt;Keystone&lt;/a&gt; for authentication, a new batch format for passing more information about non-existing metrics to create and tons of performance improvements!&lt;/p&gt;
&lt;p&gt;We already started working on the next version of Gnocchi! Come and join us on &lt;a href=&quot;http://github.com/gnocchixyz&quot;&gt;GitHub&lt;/a&gt;! Star us, and stay tuned for some more awesome news around metrics.&lt;/p&gt;
</content:encoded></item><item><title>Scaling a polling Python application with parallelism</title><link>https://julien.danjou.info/blog/scaling-python-application-threads/</link><guid isPermaLink="true">https://julien.danjou.info/blog/scaling-python-application-threads/</guid><description>A few weeks ago, Alon contacted me and asked me the following:  &gt; It so happened that I&apos;m currently working on scaling some Python app. Specifically, now I&apos;m trying to figure out the best way to scale</description><pubDate>Tue, 23 Jan 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few weeks ago, Alon contacted me and asked me the following:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It so happened that I&apos;m currently working on scaling some Python app. Specifically, now I&apos;m trying to figure out the best way to scale SSH connections - when one server has to connect to thousands (or even tens of thousands) of remote machines in a short period of time (say, several minutes).&lt;br /&gt;
How would you write an application that does that in a scalable way?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Alon is using such an application to gather information on the hosts it connects to, though that&apos;s not important in this case.&lt;/p&gt;
&lt;p&gt;In a series of blog post, I&apos;d like to help Alon solve this problem! We&apos;re gonna write an application that can manage millions of hosts.&lt;/p&gt;
&lt;p&gt;Well, if you have enough hardware, obviously.&lt;/p&gt;
&lt;h2&gt;The job&lt;/h2&gt;
&lt;p&gt;Writing a Python application that connects to a host by ssh can be done using, for example, &lt;a href=&quot;http://docs.paramiko.org/en/&quot;&gt;Paramiko&lt;/a&gt;. That will not be the focus of this blog post since it is pretty straightforward to do.&lt;/p&gt;
&lt;p&gt;To keep this exercise simple, we&apos;ll just use a &lt;code&gt;ping&lt;/code&gt; function that looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import subprocess

def ping(hostname):
    p = subprocess.Popen([&quot;ping&quot;, &quot;-c&quot;, &quot;3&quot;, &quot;-W&quot;, &quot;1&quot;, hostname],
                         stdout=subprocess.DEVNULL,
                         stderr=subprocess.DEVNULL)
    return p.wait() == 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The function &lt;code&gt;ping&lt;/code&gt; returns &lt;code&gt;True&lt;/code&gt; if the host is reachable and alive, or &lt;code&gt;False&lt;/code&gt; if an error occurs (bad hostname, network unreachable, ping timeout, etc.). We&apos;re also not trying to make &lt;code&gt;ping&lt;/code&gt; fast by specifying a lower timeout or a smaller number of packets. The goal is to scale this task while knowing it &lt;em&gt;takes time&lt;/em&gt; to execute.&lt;/p&gt;
&lt;p&gt;So &lt;code&gt;ping&lt;/code&gt; is going to be the job to be executed by our application. It&apos;ll replace &lt;code&gt;ssh&lt;/code&gt; in this example, but you&apos;ll see it&apos;ll be easy to replace it with any other job &lt;em&gt;you&lt;/em&gt; might have.&lt;/p&gt;
&lt;p&gt;We&apos;re going to use this job to accomplish a bigger mission: determine which hosts in my home network are up:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for i in range(255):
    ip = &quot;192.168.2.%d&quot; % i
    if ping(ip):
        print(&quot;%s is alive&quot; % ip)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running this program alone and pinging all 255 IP addresses takes more than 10 minutes.&lt;/p&gt;
&lt;p&gt;It is pretty slow because each time we ping a host, we wait for the ping to succeed or timeout before starting the next ping. So if you need 3 seconds to ping each host in average, then to ping 255 nodes you&apos;ll need 5 seconds × 255 = 765 seconds and that&apos;s more than 12 minutes.&lt;/p&gt;
&lt;h2&gt;The solution&lt;/h2&gt;
&lt;p&gt;If 255 hosts need 12 minutes to be pinged, you can imagine how long it&apos;s going to be when we&apos;re going to test which hosts are alive on the IPv4 Internet – 4 294 967 296 addresses to ping!&lt;/p&gt;
&lt;p&gt;Since those ping (or ssh) jobs are not CPU intensive, we can consider that one multi-processor host is going to be powerful enough – at least for a beginning.&lt;/p&gt;
&lt;p&gt;The real issue here currently is that those tasks are I/O intensive and executing them serially is &lt;em&gt;very&lt;/em&gt; long.&lt;/p&gt;
&lt;p&gt;So let&apos;s run them in parallel!&lt;/p&gt;
&lt;p&gt;To do this, we&apos;re going to use &lt;em&gt;threads&lt;/em&gt;. Threads are not efficient in Python when your tasks are CPU intensive, but in case of blocking I/O, they are good enough.&lt;/p&gt;
&lt;h2&gt;Using &lt;code&gt;concurrent.futures&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;With &lt;code&gt;concurrent.futures&lt;/code&gt;, it&apos;s easy to manage a pool of threads and schedule the execution of tasks. Here&apos;s how we&apos;re going to do it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import functools
from concurrent import futures
import subprocess

def ping(hostname):
    p = subprocess.Popen([&quot;ping&quot;, &quot;-q&quot;, &quot;-c&quot;, &quot;3&quot;, &quot;-W&quot;, &quot;1&quot;,
                          hostname],
                         stdout=subprocess.DEVNULL,
                         stderr=subprocess.DEVNULL)
    return p.wait() == 0

with futures.ThreadPoolExecutor(max_workers=4) as executor:
    futs = [
        (host, executor.submit(functools.partial(ping, host)))
        for host in (&quot;192.168.2.%d&quot; % i for i in range(255))
    ]

    for ip, f in futs:
        if f.result():
            print(&quot;%s is alive&quot; % ip)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;ThreadPoolExecutor&lt;/code&gt; is an engine, called executor, that allows us to submit tasks to it. Each task submitted is put into an internal queue using the &lt;code&gt;executor.submit&lt;/code&gt; method. This method takes a function to execute as argument.&lt;/p&gt;
&lt;p&gt;Then, the executor pulls jobs out of its queue and execute them. In order to execute them, it starts a thread that is going to be responsible for the execution. The maximum number of threads to start is controlled by the &lt;code&gt;max_workers&lt;/code&gt; parameters.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;executor.submit&lt;/code&gt; returns a &lt;code&gt;Future&lt;/code&gt; object, that holds the future result of the submitted task. &lt;code&gt;Future&lt;/code&gt; objects expose methods to know if the task is finished or not; here we just use &lt;code&gt;Future.result()&lt;/code&gt; to get the result. This method will block until the result is ready.&lt;/p&gt;
&lt;p&gt;There&apos;s no magic recipe to find how many max workers you should use. It really depends on the nature of the tasks that are submitted. In this case, using a value of 4 brings down the execution time to 3 minutes – roughly 12 minutes divided by 4, which makes sense. Setting the &lt;code&gt;max_workers&lt;/code&gt; to 255 (i.e. the number of tasks submitted) will make all the pings started at the same time, producing a CPU usage spike, but bringing down the total execution time to less than 5 seconds!&lt;/p&gt;
&lt;p&gt;Obviously, you wouldn&apos;t be able to start 4 billion threads in parallel, but if your system is big and fast enough, and your task using more I/O than CPU, you can use a pretty high value in this case. The memory should also be taken into account – in this case, it&apos;s very low since the ping task is not using a lot of memory.&lt;/p&gt;
&lt;h2&gt;Just a first step&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://scaling-python.com&quot;&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/the-hacker-guide-to-scaling-python.png&quot; alt=&quot;Cover of The Hacker&apos;s Guide to Scaling Python&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As already said, this &lt;code&gt;ping&lt;/code&gt; job does not use a lot of CPU time or I/O bandwidth, neither would the original ssh case by Alon. However, if that would be the case, this method would be limited pretty quickly. Threads are not always the best option to maximize your throughput, especially with Python.&lt;/p&gt;
&lt;p&gt;These are just the first steps of the distribution and scalability mechanism that you can implement using Python. There are a few other options available on top of this mechanism – I&apos;ve covered those in my book &lt;a href=&quot;https://scaling-python.com&quot;&gt;Scaling Python&lt;/a&gt;, if you&apos;re interested in learning more!&lt;/p&gt;
&lt;p&gt;If you&apos;re curious, go read &lt;a href=&quot;https://julien.danjou.info/blog/scaling-python-application-asyncio&quot;&gt;the next article of this series&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>A safe GitHub workflow with Pastamaker</title><link>https://julien.danjou.info/blog/pastamaker/</link><guid isPermaLink="true">https://julien.danjou.info/blog/pastamaker/</guid><description>When the Gnocchi project decided to move to GitHub, we developers had to move from a Gerrit based workflow to a GitHub pull-request one.</description><pubDate>Fri, 15 Dec 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When the &lt;a href=&quot;https://gnocchi.xyz&quot;&gt;Gnocchi&lt;/a&gt; project decided to move to &lt;a href=&quot;https://github.com&quot;&gt;GitHub&lt;/a&gt;, we developers had to move from a Gerrit based workflow to a GitHub pull-request one.&lt;/p&gt;
&lt;p&gt;This has been challenging in some ways. We were satisfied with the workflow we had using Gerrit and &lt;a href=&quot;https://docs.openstack.org/infra/zuul/&quot;&gt;Zuul&lt;/a&gt; for testing so we decided to adapt GitHub to our requirements.&lt;/p&gt;
&lt;p&gt;We know that Zuul now supports GitHub. However, that implies having your own testing infrastructure, something we can&apos;t afford. Instead, we rely on &lt;a href=&quot;http://travis-ci.org&quot;&gt;Travis&lt;/a&gt;, like most open-source projects hosted on GitHub.&lt;/p&gt;
&lt;h2&gt;The workflow&lt;/h2&gt;
&lt;p&gt;The workflow we wanted to have was the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A contributor creates a pull-request on GitHub.&lt;/li&gt;
&lt;li&gt;The pull-request is tested by Travis.&lt;/li&gt;
&lt;li&gt;The pull-request is reviewed by approved projects members.&lt;/li&gt;
&lt;li&gt;If the tests pass and two reviewers have approved the pull-request, then it&lt;br /&gt;
can be merged.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This sounds simple, but it is actually not &lt;em&gt;that&lt;/em&gt; simple.&lt;/p&gt;
&lt;p&gt;First, when Travis tests the pull-request, it checks what has been sent by the contributor. If the contributor created a pull-request on top of an outdated version of the base branch, that&apos;s what will be tested by Travis during the initial pull-request creation.&lt;/p&gt;
&lt;p&gt;Even if the pull-request has been created using the tip of the base branch, as time passes, the base branch will progress. However, the pull-request created by your contributor will not get those new commits – unless rebased manually.&lt;/p&gt;
&lt;p&gt;That means the Travis tests result is now outdated invalid. Still, GitHub and Travis will both show you that this pull-request passed all tests – yes it &lt;em&gt;did&lt;/em&gt; but with an old base branch from a while back!&lt;/p&gt;
&lt;p&gt;If you added new tests in the meantime in your base branch, it&apos;s possible that this pull-request does not work anymore. Pressing the &lt;em&gt;merge&lt;/em&gt; button might just break your project!&lt;/p&gt;
&lt;p&gt;To help with that problem, GitHub recently added a button that allows you to &lt;em&gt;base branch into the pull-request&lt;/em&gt;. That allows, in one click, to get the pull-requested updated with the base branch (e.g., &lt;em&gt;master&lt;/em&gt;) and retested by Travis.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/github-update-pr-button.png&quot; alt=&quot;github-update-pr-button&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Still, this means that if you have ten pull-requests, you need to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Merge base branch into PR#1&lt;/li&gt;
&lt;li&gt;Wait for Travis to pass&lt;/li&gt;
&lt;li&gt;Wait for two reviewers to approve&lt;/li&gt;
&lt;li&gt;Merge PR#1&lt;/li&gt;
&lt;li&gt;All other nine pull-requests are not out of date. You need to do start back at operation 1. for each pull-request.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is very tedious to do manually, especially when your projects has tons of pull-requests.&lt;/p&gt;
&lt;p&gt;This is why &lt;a href=&quot;http://blog.sileht.net&quot;&gt;Mehdi Abaakouk&lt;/a&gt; created &lt;a href=&quot;http://github.com/sileht/pastamaker&quot;&gt;Pastamaker&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Pastamaker to the rescue&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://github.com/sileht/pastamaker&quot;&gt;Pastamaker&lt;/a&gt; is a small Web application that implements the described workflow. Once connected to your GitHub project, it will set the proper permissions to protect it for accidental manual merge and force the workflow above to be followed.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/pastamaker-pr-1.png&quot; alt=&quot;pastamaker-pr-1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Pastamaker listens for GitHub and Travis events to track the state of each pull-request. If it detects that a pull-request has been approved by two reviewers and that the initial Travis test run passed, it will merge the base branch if needed in it, wait for Travis to pass again, and then finally merge it.&lt;/p&gt;
&lt;p&gt;If multiple pull-requests are approved at the same time and are candidates for a merge, it will order them, update once at a time, wait for Travis results and merge them if they pass. It essentially automates the workflow described above.&lt;/p&gt;
&lt;p&gt;Pastamaker exposes its data via a simple dashboard, which allows seeing all the pull-requests for your project in a snap.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/pastamaker.png&quot; alt=&quot;pastamaker&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Pastamaker offers a lot of tiny other details that make the developers lives easier, such as posting the job result with direct links to the jobs logs in the pull-request – so you&apos;re informed as soon as they pass or fail and can fix them right away!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://github.com/sileht/pastamaker&quot;&gt;Pastamaker&lt;/a&gt; is obviously open-source, and we would love to see you give it a try!&lt;/p&gt;
</content:encoded></item><item><title>Scaling Python released</title><link>https://julien.danjou.info/blog/scaling-python-released/</link><guid isPermaLink="true">https://julien.danjou.info/blog/scaling-python-released/</guid><description>I am proud to announce today the immediate release of Scaling Python, my second book about Python! It talks about the distribution and performance of applications written in Python, and how to build.</description><pubDate>Tue, 05 Dec 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I am proud to announce today the immediate release of &lt;em&gt;&lt;a href=&quot;https://scaling-python.com&quot;&gt;Scaling Python&lt;/a&gt;&lt;/em&gt;, my second book about Python! It talks about the distribution and performance of applications written in Python, and how to build them properly!&lt;/p&gt;
&lt;p&gt;It took me a year to build this entirely new product around Python. It&apos;s an exciting moment and I am sure it will enjoy many of my dear readers that are waiting for it for a while now!&lt;/p&gt;
&lt;p&gt;I&apos;ve been able to build this using my last three years of experience working on &lt;em&gt;&lt;a href=&quot;http://thehackerguidetopython.com&quot;&gt;The Hacker&apos;s Guide to Python&lt;/a&gt;&lt;/em&gt; – an amazing adventure.&lt;/p&gt;
&lt;p&gt;Starting now, you can enjoy reading the book and learn a bit more about building distributed and scalable applications with Python. I really hope it&apos;ll help you bring your Python-fu to a new level, and that it will help you build great projects!&lt;/p&gt;
&lt;p&gt;Since this is first days of sale, you will enjoy &lt;strong&gt;a 15% discount&lt;/strong&gt; on all packages for the next 48 hours!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://scaling-python.com&quot;&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/the-hacker-guide-to-scaling-python.png&quot; alt=&quot;Cover of The Hacker&apos;s Guide to Scaling Python&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>Scaling Python: the interviewees</title><link>https://julien.danjou.info/blog/scaling-python-interviews/</link><guid isPermaLink="true">https://julien.danjou.info/blog/scaling-python-interviews/</guid><description>The release date for Scaling Python is now very close! Today, I&apos;d like to talk a bit about the interviews that I&apos;ve run those last months that are featured in the book.</description><pubDate>Tue, 28 Nov 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The release date for &lt;a href=&quot;http://scaling-python.com&quot;&gt;Scaling Python&lt;/a&gt; is now very close! Today, I&apos;d like to talk a bit about the interviews that I&apos;ve run those last months that are featured in the book.&lt;/p&gt;
&lt;p&gt;I&apos;m glad that during those long weeks work, I have managed to find a Python expert on each of the major topic covered in the book. They will provide hindsight on the different subject covered and share their experience so you can benefit from it!&lt;/p&gt;
&lt;p&gt;Without further delay, ladies and gentlemen, here they are:&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;# Mehdi Abaakouk &lt;img src=&quot;https://julien.danjou.info/content/images/03/mabaakouk.png&quot; alt=&quot;Mehdi Abaakouk portrait&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Mehdi is a French free software hacker, working at Red Hat, who has been using Linux for almost twenty years now. He works daily on OpenStack, the largest open source project using Python. He also regularly builds and contribute to distributed applications and is responsible for several widely used Python libraries – &lt;em&gt;Cotyledon&lt;/em&gt;, &lt;em&gt;oslo.messaging&lt;/em&gt;, etc.&lt;/p&gt;
&lt;p&gt;In the book, Mehdi gives excellent tips on how to build distributed daemons.&lt;/p&gt;
&lt;h2&gt;Naoki Inada&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/ninada.png&quot; alt=&quot;Naoki Inada portrait&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Naoki is a Japanese software engineer, who happens to also be one of the CPython developers. He worked on several significant features in CPython, such as &lt;em&gt;asyncio&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;You&apos;ll be able to read Naoki opinion on Python and other programming languages when it comes to asynchronous workflows.&lt;/p&gt;
&lt;h2&gt;Chris Dent&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/cdent.png&quot; alt=&quot;Chris Dent portrait&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Chris Dent has been using Python for more than 15 years now and is an expert on WSGI. He has an extensive knowledge about REST API – he is one of the early organizers of the &lt;a href=&quot;https://specs.openstack.org/openstack/api-wg/&quot;&gt;OpenStack API working group&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Chris has, among other things, created Gabbi, a fabulous Python testing tool for. In &lt;em&gt;Scaling Python&lt;/em&gt;, he provides best practice on building REST API.&lt;/p&gt;
&lt;h2&gt;Joshua Harlow&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/harlowja.png&quot; alt=&quot;Joshua Harlow portrait&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Joshua is a highly experienced engineer in distributed systems. He maintains a few Python libraries, such as &lt;em&gt;Kazoo&lt;/em&gt; (ZooKeeper client) or &lt;em&gt;TaskFlow&lt;/em&gt; (distributed tasks).&lt;/p&gt;
&lt;p&gt;In the book, Joshua lays down principles that make Python application resilient and fault tolerant.&lt;/p&gt;
&lt;h2&gt;Alexys Jacob-Monier&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/ajacobmonier.png&quot; alt=&quot;Alexys Jacob-Monier portrait&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Alexys is the CTO of 1000mercis and is part of the open-source software community for a few years now. He regularly gives speeches at Python conferences and talks about how to leverage Python when distributing applications.&lt;/p&gt;
&lt;p&gt;Alexys talks about advanced techniques, e.g. using consistent hash rings, and how they should be applied.&lt;/p&gt;
&lt;h3&gt;Victor Stinner&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/vstinner.png&quot; alt=&quot;Victor Stinner portrait&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Victor is a long time CPython core developer, working on the language itself for several years now. He is well known in the community for working on making CPython faster and leads several performance-oriented projects.&lt;/p&gt;
&lt;p&gt;In &lt;em&gt;Scaling Python&lt;/em&gt;, Victor talks about optimizations, profiling, and performance when using Python, and how to make the right decisions.&lt;/p&gt;
&lt;h3&gt;Jason Myers&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/jmyers.png&quot; alt=&quot;Jason Myers portrait&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Jason is a Python developer and an author – he wrote an entire book on SQLAlchemy, the famous Python SQL library. He worked on cloud computing platforms, as a Web developer, and as a data engineer.&lt;/p&gt;
&lt;p&gt;In the book, we discuss with Jason about caching and RDBMS usage.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;a href=&quot;https://scaling-python.com&quot;&gt;&lt;img src=&quot;https://scaling-python.com/img/the-hacker-guide-to-scaling-python.png&quot; alt=&quot;Cover of The Hacker&apos;s Guide to Scaling Python&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It was marvelous to have a chat with all those developers and pick their brain about different subjects. These contents broaden the scope and expand the view of the themes covered through the chapters. I can&apos;t thank them all enough!&lt;/p&gt;
&lt;p&gt;If you want to be informed of the release of the book, subscribe in the following form! You&apos;ll be the first to be notified and to enjoy an exclusive offer. ;-)&lt;/p&gt;
</content:encoded></item><item><title>Mastering PostgreSQL</title><link>https://julien.danjou.info/blog/mastering-postgresql/</link><guid isPermaLink="true">https://julien.danjou.info/blog/mastering-postgresql/</guid><description>A few months ago, my friend Dimitri Fontaine and I discussed writing books and sharing our knowledge.</description><pubDate>Tue, 07 Nov 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few months ago, my friend &lt;a href=&quot;http://tapoueh.org&quot;&gt;Dimitri Fontaine&lt;/a&gt; and I discussed writing books and sharing our knowledge. If you do not know Dimitri yet, he is an old-time PostgreSQL Major Contributor – meaning he writes code for the PostgreSQL software itself!&lt;/p&gt;
&lt;p&gt;I interviewed Dimitri a few years ago in &lt;a href=&quot;https://thehackerguidetopython.com&quot;&gt;The Hacker&apos;s Guide to Python&lt;/a&gt;, where he shared his insight about writing proper Python application code with relational database management system.&lt;/p&gt;
&lt;p&gt;All of this gave Dimitri the idea of writing his own book about PostgreSQL. And he released his book this week! If like me, you can&apos;t wait to read it, just scroll down below and grab a package with a &lt;strong&gt;15% discount&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;To celebrate the event, I went ahead and decided to ask him a few questions.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/dim-rounded.png&quot; alt=&quot;Dimitri Fontaine portrait&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;Hey Dimitri! So what made you start writing this book in the first place?&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;Dimitri&lt;/em&gt;: As a PostgreSQL consultant, I&apos;ve met with many developers for whom SQL just didn&apos;t click. They then tend to consider SQL much as they would consider HTML: some string you need to build dynamically then send over to an external part of the system, either the browser or the database server.&lt;/p&gt;
&lt;p&gt;As soon as you start on this path, SQL is more and more of a problem in your daily life and developer workflow. It doesn&apos;t integrate well with the usual testing and continuous integration tools, not to mention it&apos;s hard to review (as in code review) and hard to maintain.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;At the end of the day, when using a relational database system, you have to&lt;br /&gt;
know your SQL.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As a developer, you need to be fluent in SQL and master window functions, common table expressions, recursive queries, time zone handling and advanced string and regexp processing functions, transaction behaviors and also how to build a query result set in JSON. And so much more.&lt;/p&gt;
&lt;p&gt;For most developers, it&apos;s a daunting task. Just too much to learn when they have so many other things to take care of. So many lines of code to write to implement that new product idea. Well, as Dijkstra put it, lines of code are “spent” on writing a new feature. When you master SQL, you spend much less of those lines of code.&lt;/p&gt;
&lt;p&gt;So I wrote &lt;strong&gt;Mastering PostgreSQL in Application Development&lt;/strong&gt; to teach SQL to developers. Focusing on real use cases and authentic data set, so that it&apos;s easier to grasp all those advanced features. The book also addresses the tooling you need to integrate SQL as another programming language with a decent worfklow, from code review to unit testing, including regression testing and production debugging.&lt;/p&gt;
&lt;h4&gt;Who should read this book? What are the prerequisites to get the most of it?&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;Dimitri&lt;/em&gt;: The pre-requisites are quite easy to reach.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you ever deployed an application that embeds SQL queries and talks to a&lt;br /&gt;
database server, you&apos;re in the target audience, the book is for you.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you&apos;ve never used PostgreSQL before, reading &lt;strong&gt;Mastering PostgreSQL in Application Development&lt;/strong&gt; may convince you to have a look at that awesome piece of technology. My bet is on you switching to PostgreSQL and finding it much better at helping you in your daily work and challenges.&lt;/p&gt;
&lt;p&gt;Even if you&apos;ve been using MySQL all your life, you will learn about standard SQL features and how to use them in a way that applies to more than just PostgreSQL, so the book is going to help you in your daily life.&lt;/p&gt;
&lt;h4&gt;What&apos;s your next adventure now that this book is out?&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;Dimitri&lt;/em&gt;: There are more things that I want to do that a lifetime allows, and I am in the process of choosing what is going to be my next adventure. I feel so lucky to be able to have that problem to solve… and it still isn&apos;t the easiest one for me.&lt;/p&gt;
&lt;p&gt;What I can tell you is that I have much more PostgreSQL knowledge to share after having been using, promoting, and contributing to this database server technology for about 20 years now. So if that first book sells well, I will get back to filling empty pages and deliver more contents to help developers making the best of SQL, to help developers on their road to Mastering PostgreSQL!&lt;/p&gt;
&lt;h4&gt;Thanks Dimitri!&lt;/h4&gt;
&lt;p&gt;I&apos;ve just read the book and found it fantastic. It contains tons of tips on how to use PostgreSQL correctly, and I discovered SQL features I had no clue about. The book uses real data that you can fetch and play with. It provides the data and a Docker container with everything included so you can edit the query yourself and try it out. There is no better way to learn things than to play with the examples that are included, in just a few clicks!&lt;/p&gt;
&lt;p&gt;The book also features a few interviews with SQL experts from the PostgreSQL community and from the development community, which gives great insight about how to use the software.&lt;/p&gt;
&lt;p&gt;Dimitri is offering &lt;strong&gt;15% off for my readers&lt;/strong&gt; during the next 48 hours for any of the edition of the book. Just use the &lt;strong&gt;PYTHON-LOVES-POSTGRESQL&lt;/strong&gt; coupon code in any of the following package:&lt;/p&gt;
&lt;p&gt;.product img { max-width: 100%; margin-top: 10px; } .col-sm-1 { width: 8.33333333%; float: left; } .col-sm-3 { width: 25%; float: left; } .col-sm-9 { width: 75%; float: left; } .col-sm-11 { width: 91.66666667%; float: left; /* should be on the div not here but well.. */ padding-left: 30px; padding-bottom: 20px; } .row { clear: both; } .btn.btn-default { background-color: #e6940e; color: #FFF; line-height: 46px; height: 50px; font-size: 19px; cursor: pointer; text-align: center; border-radius: 5px; border: 3px solid #e6940e; vertical-align: middle; display: inline-block; padding: 0 30px; position: relative; outline: none !important; transition: color .3s ease,background .3s ease,border-color .3s ease,opacity .3s ease; box-shadow: none; }&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gumroad.com/a/623817843/szoX&quot;&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/MasteringPostgreSQLinAppDev-Cover.png&quot; alt=&quot;Mastering PostgreSQL – Enterprise Edition&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://gumroad.com/a/623817843/szoX&quot;&gt;&lt;/a&gt;Mastering PostgreSQL – Enterprise Edition&lt;/h3&gt;
&lt;p&gt;$179 &lt;strong&gt;$152&lt;/strong&gt;&lt;br /&gt;
with coupon code &lt;strong&gt;PYTHON-LOVES-POSTGRESQL&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The Enterprise Edition includes:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/ebook_reader.png&quot; alt=&quot;E-book reader icon&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The book in PDF, EPUB and MOBI formats.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/icon_mic.png&quot; alt=&quot;Microphone icon&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Interviews from industry veterans who began building web application in the previous century. They&apos;ve been there and have opinions to share about how to approach SQL.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/icon_database.png&quot; alt=&quot;Database icon&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The PostgreSQL database dump that you need to run the queries against, with a script to restore it easily. The database includes all the 12 datasets used in the book.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/docker-logo.png&quot; alt=&quot;Docker logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;A Docker container image of an already loaded PostgreSQL database with the whole 12 datasets in 56 tables, and the 265 SQL queries each in their own .sql file and a Web-based application for easily running and editing the SQL queries.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/certificate.png&quot; alt=&quot;Certificate icon&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Licence for you to share the book and the Docker set-up with up to 50 people, including you. That&apos;s everything you need for your whole team to master SQL!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gumroad.com/a/623817843/szoX&quot;&gt;Buy the Enterprise Edition&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;a href=&quot;https://gumroad.com/a/623817843/koprL&quot;&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/MasteringPostgreSQLinAppDev-Cover.png&quot; alt=&quot;Mastering PostgreSQL – Full Edition&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://gumroad.com/a/623817843/koprL&quot;&gt;&lt;/a&gt;Mastering PostgreSQL – Full Edition&lt;/h3&gt;
&lt;p&gt;$89 &lt;strong&gt;$75&lt;/strong&gt;&lt;br /&gt;
with coupon code &lt;strong&gt;PYTHON-LOVES-POSTGRESQL&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The Full Edition includes:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/ebook_reader.png&quot; alt=&quot;E-book reader icon&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The book in PDF, EPUB and MOBI formats.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/icon_mic.png&quot; alt=&quot;Microphone icon&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Interviews from industry veterans who began building web application in the previous century. They&apos;ve been there and have opinions to share about how to approach SQL.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/icon_database.png&quot; alt=&quot;Database icon&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The PostgreSQL database dump that you need to run the queries against, with a script to restore it easily. The database includes all the 12 datasets used in the book.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gumroad.com/a/623817843/koprL&quot;&gt;Buy the Full Edition&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;a href=&quot;https://gumroad.com/a/623817843/WhNVv&quot;&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/MasteringPostgreSQLinAppDev-Cover.png&quot; alt=&quot;Mastering PostgreSQL&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;&lt;a href=&quot;https://gumroad.com/a/623817843/WhNVv&quot;&gt;&lt;/a&gt;Mastering PostgreSQL&lt;/h3&gt;
&lt;p&gt;$39 &lt;strong&gt;$33&lt;/strong&gt;&lt;br /&gt;
with coupon code &lt;strong&gt;PYTHON-LOVES-POSTGRESQL&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The Standard Edition includes:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/ebook_reader.png&quot; alt=&quot;E-book reader icon&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The book in PDF, EPUB and MOBI formats.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/icon_mic.png&quot; alt=&quot;Microphone icon&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Interviews from industry veterans who began building web application in the previous century. They’ve been there and have opinions to share about how to approach SQL.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gumroad.com/a/623817843/WhNVv&quot;&gt;Buy the book&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;If you have any question, feel free to reach &lt;a href=&quot;mailto:dim@tapoueh.org&quot;&gt;Dimitri&lt;/a&gt; directly and he will be happy to reply. Or write in the comment section below!&lt;/p&gt;
&lt;p&gt;And don&apos;t worry: if the book is not what you expect it to be and has no value to you, then just say so and Dimitri will refund you, no questions asked.&lt;/p&gt;
</content:encoded></item><item><title>Gnocchi 4.1 is out</title><link>https://julien.danjou.info/blog/gnocchi-4-1-release/</link><guid isPermaLink="true">https://julien.danjou.info/blog/gnocchi-4-1-release/</guid><description>We did it again. A bit more of our usual four months were needed to do it, but Gnocchi 4.1 has been released. This is a great news and another big milestone for the project!  As usual, we enhanced Gno</description><pubDate>Fri, 27 Oct 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We did it again. A bit more of our usual four months were needed to do it, but Gnocchi 4.1 has been released. This is a great news and another big milestone for the project!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-logo.png&quot; alt=&quot;Gnocchi logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As usual, we enhanced Gnocchi and added a bunch of new things that &lt;a href=&quot;http://gnocchi.xyz/releasenotes/4.1.html&quot;&gt;can all be seen in the online changelog&lt;/a&gt;. Nevertheless, I would like to talk of a few here!&lt;/p&gt;
&lt;p&gt;First, we added notification support to the Redis incoming driver. This feature makes sure that, when using Redis as an incoming measure driver, the metrics as processed as fast as possible, rather than waiting &lt;code&gt;metric_processing_delay&lt;/code&gt;. This moves the incoming driver toward more of a push model than a pull model – even if it still uses both. That feature decreases the latency between the time metrics are pushed, and metrics are processed by &lt;em&gt;metricd&lt;/em&gt;, which is transcendent.&lt;/p&gt;
&lt;p&gt;Secondly, the internal computing engine (measures aggregation) has entirely been ported from &lt;a href=&quot;http://pandas.pydata.org&quot;&gt;Pandas&lt;/a&gt; to &lt;a href=&quot;http://numpy.org&quot;&gt;Numpy&lt;/a&gt;. While Pandas is written using Numpy, it does some things more than Numpy itself when used. Those features are beneficial when quickly writing data analysis processes, but are not needed for Gnocchi. They take CPU time, which means less throughput for &lt;em&gt;metricd&lt;/em&gt;. Pandas is still needed for the old and deprecated dynamic aggregation feature and will be entirely removed as a dependency in the next version of Gnocchi.&lt;/p&gt;
&lt;p&gt;Finally, the biggest functionality that has landed is &lt;a href=&quot;http://gnocchi.xyz/rest.html#aggregates-on-the-fly-measurements-modification-and-aggregation&quot;&gt;the new &lt;code&gt;/v1/aggregates&lt;/code&gt; endpoint&lt;/a&gt;. This is a principal feature that allows to retrieve aggregates but also to do cross-aggregation in new ways that were not possible before. For example, you can request &quot;the absolute value of the average of two metrics being multiplied by&quot; writing: &lt;code&gt;(absolute (* (metric 32dd0731-c423-45aa-94f6-e4069989eb57 mean) (metric 942990de-b208-4bf7-a0ee-93e4890df73a mean)))&lt;/code&gt;. This endpoint supports fetching any metric from the database (by id or by search in resources) and applying any mathematics operation. The syntax is inspired from Lisp, which makes it easy to write both as a string or as JSON.&lt;/p&gt;
&lt;p&gt;Come and join us on &lt;a href=&quot;http://github.com/gnocchixyz&quot;&gt;GitHub&lt;/a&gt;! Star us, and stay tuned for some more awesome news around metrics.&lt;/p&gt;
</content:encoded></item><item><title>My interview with Cool Python Codes</title><link>https://julien.danjou.info/blog/interview-coolpythoncodes/</link><guid isPermaLink="true">https://julien.danjou.info/blog/interview-coolpythoncodes/</guid><description>A few days ago, I&apos;ve recently been contacted by Godson Rapture from Cool Python codes to answer a few questions about what I work on in open source.</description><pubDate>Thu, 05 Oct 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few days ago, I&apos;ve recently been contacted by Godson Rapture from &lt;a href=&quot;http://coolpythoncodes.com/&quot;&gt;Cool Python codes&lt;/a&gt; to answer a few questions about what I work on in open source. Godson regularly interview developers and I invite you to check out his website!&lt;/p&gt;
&lt;p&gt;Here&apos;s a copy of &lt;a href=&quot;http://coolpythoncodes.com/julien-danjou/&quot;&gt;my original interview&lt;/a&gt;. Enjoy!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Good day, Julien Danjou, welcome to Cool Python Codes. Thanks for taking your precious time to be here.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You’re welcome!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Could you kindly tell us about yourself like your full name, hobbies, nationality, education, and experience in programming?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Sure. I’m Julien Danjou, I’m French and live in Paris, France. I studied Computer science for 5 years around 15 years ago, and continued my career in that field since then, specializing in open source projects.&lt;/p&gt;
&lt;p&gt;Those last years, I’ve been working as a software engineer at Red Hat. I’ve spent the last 10 years working with the Python programming language. Now I work on the Gnocchi project which is a time series database.&lt;/p&gt;
&lt;p&gt;When I’m not coding, I enjoy running half-marathon and playing FPS games.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/pyconfr-2017-jd.jpg&quot; alt=&quot;Julien Danjou at PyCon France 2017&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Can you narrate your first programming experience and what got you to start learning to program?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I started programming around 2001, and my first serious programs were in Perl. I was contributing to a hosting platform for free software named VHFFS. It was a free software project itself, and I enjoyed being able to learn from other more experienced developers and being able to contribute back to it. That’s what got me stuck into that world of open source projects.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Which programming language do you know and which is your favorite?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I know quite a few, I’ve been doing serious programming in Perl, C, Lua, Common Lisp, Emacs Lisp and Python.&lt;/p&gt;
&lt;p&gt;Obviously, my favorite is Common Lisp, but I was never able to use it for any serious project, for various reasons. So I spend most of my time hacking with Python, which I really enjoy as it is close to Lisp, in some ways. I see it as a small subset of Lisp.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What inspired you to venture into the world of programming and drove you to learn a handful of programming languages?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It was mostly scratching my own itches when I started. Each time I saw something I wanted to do or a feature I wanted in an existing software, I learned what I needed to get going and get it working.&lt;/p&gt;
&lt;p&gt;I studied C and Lua while writing awesome- the window manager that I created 10 years ago and used for a while. I learned Emacs Lisp while writing extensions that I wanted to see in Emacs, etc. It’s the best way to start.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What is your blog about?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;My blog is a platform where I write about what I work on most of the time. Nowadays, it’s mostly about Python and the main project I contribute to,&lt;br /&gt;
Gnocchi.&lt;/p&gt;
&lt;p&gt;When writing about Gnocchi, I usually try to explain what part of the project I worked on, what new features we achieved, etc.&lt;/p&gt;
&lt;p&gt;On Python, I try to share solutions to common problems I encountered or identified while doing e.g. code reviews. Or presenting a new library I created!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Tell us more about your book, The Hacker’s Guide to Python.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It’s a compilation of everything I learned those last years building large Python applications. I spent the last 6 years developing on a large code base with thousands of other developers.&lt;/p&gt;
&lt;p&gt;I’ve reviewed tons of code and identified the biggest issues, mistakes, and bad practice that developers tend to have. I decided to compile that in a guide, helping developers that played a bit with Python to learn the stages to get really productive with Python.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;OpenStack is the biggest open source project in Python, Can you tell us more about OpenStack?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;OpenStack is a cloud computing platform, started 7 years ago now. Its goal is to provide a programmatic platform to manage your infrastructure while being open source and avoiding vendor lock-in.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Who uses OpenStack? Is it for programmers, website owners?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It’s used by a lot of different organizations – not really by individuals. It’s a big piece of software. You can find it in some famous public cloud providers (Dreamhost, Rackspace…), and also as a private cloud in a lot of different organizations, from Bloomberg to eBay or the CERN in Switzerland, a big OpenStack user. Tons of telecom providers also leverages OpenStack for their own internal infrastructure.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Have you participated in any OpenStack conference? What did you speak on if&lt;br /&gt;
you did?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I’ve attended the last 9 OpenStack summits and a few other OpenStack events around the world. I’ve been engaged in the upstream community for the last 6 years now.&lt;/p&gt;
&lt;p&gt;My area of expertise is telemetry, the stack of software that is in charge of collecting and storing metrics from the various OpenStack components. This is what I regularly talk about during those events.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;How can one join the OpenStack community?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There’s an entire documentation about that, called the &lt;a href=&quot;https://docs.openstack.org/infra/manual/developers.html&quot;&gt;Developer’s Guide&lt;/a&gt;. It explains how to setup your environment to send patches, how to join the community using the mailing-lists or IRC.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What makes your book, &lt;a href=&quot;https://thehackerguidetopython.com&quot;&gt;The Hacker’s Guide to Python&lt;/a&gt; stand out from other Python books? Also, who exactly did you write this book for?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I wrote the book that I always wanted to read about Python, but never found. It’s not a book for people that want to learn Python from scratch. It’s a great guide for those who know the language but don’t know the details that experienced developers know and that make the difference. The best practice, the elegant solutions to common problems, etc. That’s why it also includes interviews with prominent Python developers, so they can share their advice on different areas.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;How can someone get your book?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I’ve decided to self-publish my book, so he does not have an editor like you can be used to see. The best place to get it is online at where you can pick the format you want, electronic or paper.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What do you mean when you say you hack with Python?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Unfortunately, most people refer to hacking as the activity of some bad guys trying to get access to whatever they’re not supposed to see. In the book title, I mean “hacking” as the elegant way of writing code and making things worse smoothly even when you were not expecting to make it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You mentioned earlier that Gnocchi is a time series database. Can you please be more elaborate about Gnocchi? Is there also any documentation about Gnocchi?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So Gnocchi is a project I started a few years ago to store time series at large scale. Timeseries are basically a series of tuple composed of a timestamp and a value.&lt;/p&gt;
&lt;p&gt;Imagine you wanted to store the temperature of all the rooms of the world at any point of time. You’d need a dedicated database for that with the right data structure. This is what Gnocchi does: it provides this data structure storage at very, very large scale.&lt;/p&gt;
&lt;p&gt;The primary use case is infrastructure monitoring, so most people use it to store tons of metrics about their hardware, software, etc. It’s fully documented on &lt;a href=&quot;http://gnocchi.xyz&quot;&gt;its website&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;How can a programmer without much experience contribute to open source projects?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The best way to start is to try to fix something that irritates you in some way. It might be a bug, it might be a missing feature. Start small. Don’t try big things first or you could be discouraged.&lt;/p&gt;
&lt;p&gt;Never stop.&lt;/p&gt;
&lt;p&gt;Also, don’t plunge right away in the community and start poking random people or spam them with questions. Do your homework, and listen to the community for a while to get a sense of how things are going. That can be joining IRC and lurking or following the mailing lists for example.&lt;/p&gt;
&lt;p&gt;Big open source communities dedicate programs to help you become engaged. It might be worth a try. Generic programs like Outreachy or Google Summer of Code are a great way to start if you don’t feel confident enough to jump by your own means in a community.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Just out of curiosity, do you write code in French?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Never ever. I think it’s acceptable to write in your language if you are sure that your code will never be open sourced and that your whole team is talking in that language, no matter what – but it’s a ballsy assumption, clearly.&lt;/p&gt;
&lt;p&gt;Truth is that if you do open source, English is the standard, so go with it. Be sad if you want, but please be pragmatic.&lt;/p&gt;
&lt;p&gt;I’ve seen projects being open sourced by companies where all the code source comments were in Korean. It was impossible for any non-Korean people to get a glance of what the code and the project was doing, so it just failed and disappeared.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;How does a team of programmers handle bugs in a large open source project?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I wish there was some magic recipe, but I don’t think it’s the case. What you want is to have a place where your users can feel safe reporting bugs. Include a template so they don’t forget any details: how to reproduce the bugs, what they expected, etc. The worst thing is to have users reporting “That does not work.” with no details. It’s a waste of time.&lt;/p&gt;
&lt;p&gt;What tool to use to log all of that really depends on the team size and culture.&lt;/p&gt;
&lt;p&gt;Once that works, the actual fixing of bug doesn’t follow any rule. Most developers fix the bug they encounter or the ones that are the most critical for users. Smaller problems might not be fixed for a long time.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Can you tell us about the new book you are working on and when do we expect&lt;br /&gt;
to get it?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That new book is entitled &lt;a href=&quot;https://scaling-python.com&quot;&gt;“Scaling Python”&lt;/a&gt; and it provides insight into how to build largely scalable and distributed applications using Python.&lt;/p&gt;
&lt;p&gt;It is also based on my experience in building this kind of software during the past years. This book also includes interviews of great Python hackers who work on scalable system or know a thing or two about writing applications for performance – an important point to have scalable applications.&lt;/p&gt;
&lt;p&gt;The book is in its final stage now, and it should be out at the beginning of 2018.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;How can someone get in contact with you?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I’m reachable at &lt;a href=&quot;mailto:julien@danjou.info&quot;&gt;julien@danjou.info&lt;/a&gt; by email or via Twitter, &lt;a href=&quot;https://twitter.com/juldanjou&quot;&gt;@juldanjou&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Gnocchi 4 performance</title><link>https://julien.danjou.info/blog/gnocchi-4-performance/</link><guid isPermaLink="true">https://julien.danjou.info/blog/gnocchi-4-performance/</guid><description>It has been a long time since I have tested Gnocchi performances. Last time was two years ago, on version 2. The current version for Gnocchi is 4.0, released a couple of months ago.</description><pubDate>Mon, 11 Sep 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It has been a long time since I have tested &lt;a href=&quot;http://gnocchi.xyz&quot;&gt;Gnocchi&lt;/a&gt; performances. &lt;a href=&quot;https://julien.danjou.info/blog/2015/gnocchi-benchmarks&quot;&gt;Last time was two years ago, on version 2&lt;/a&gt;. The current version for Gnocchi is 4.0, &lt;a href=&quot;https://julien.danjou.info/blog/2017/gnocchi-4-release&quot;&gt;released a couple of months ago&lt;/a&gt;. It adds a lot of new features, such as a &lt;a href=&quot;http://redis.org&quot;&gt;Redis&lt;/a&gt; incoming driver and a new job distribution method.&lt;/p&gt;
&lt;p&gt;Many of those features and improvement implemented over the last couple of years were made with performance in mind. It is time to check if this lives up to our expectation.&lt;/p&gt;
&lt;h3&gt;Test protocol&lt;/h3&gt;
&lt;p&gt;I have pulled the servers I used a couple of years ago out of the dust, updated them with latest RHEL 7 and installed Gnocchi 4.0.1 and Redis 4.0.1 on one of them. I used the other server as the benchmark client, in charge of generating a bunch of loads.&lt;/p&gt;
&lt;p&gt;The hardware configuration for each server is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;2 × Intel(R) Xeon(R) CPU E5-2609 v3 @ 1.90GHz (6 cores each)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;32 GB RAM&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SanDisk Extreme Pro 240GB SSD&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have installed Gnocchi using &lt;code&gt;pip install gnocchi[postgresql,file,redis]&lt;/code&gt;, created a PostgreSQL database and wrote the following configuration file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[indexer]
url = postgresql://root:@localhost/gnocchi

## Uncomment when testing with Redis
## [incoming]
## driver = redis

[storage]
file_basepath = /root/gnocchi-venv/data
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The perk of having good default values: you only to write a couple of configuration lines to get it working.&lt;/p&gt;
&lt;p&gt;I have used uWSGI as the Web server, using the configuration&lt;br /&gt;
file &lt;a href=&quot;http://gnocchi.xyz/operating.html#running-api-as-a-wsgi-application&quot;&gt;provided Gnocchi&apos;s documentation&lt;/a&gt; and configured it with 64 processes and 16 threads.&lt;/p&gt;
&lt;p&gt;Since the hardware configurations are identical, I allow myself in this article to compare the performances of Gnocchi 2 and Gnocchi 4 directly.&lt;/p&gt;
&lt;h3&gt;Benchmark tools&lt;/h3&gt;
&lt;p&gt;For generating loads, I have reused the code that I wrote and merged&lt;br /&gt;
in &lt;a href=&quot;http://pypi.python.org/gnocchiclient&quot;&gt;python-gnocchiclient&lt;/a&gt;. It is still not that easy to generate a lot of parallel loads in Python, though it is still the best tool I find available that was not too complicated to setup for things like CRUD operations.&lt;/p&gt;
&lt;p&gt;To benchmark measures, I needed something very fast to generate requests on the client side to be sure to be able to overload the server. I have leveraged &lt;a href=&quot;https://github.com/wg/wrk&quot;&gt;wrk&lt;/a&gt;, which is written in C++ and is fast. It is scriptable using Lua, so it made it easy to generate fake batches of data.&lt;/p&gt;
&lt;h3&gt;Metric CRUD operations&lt;/h3&gt;
&lt;p&gt;The first step is to benchmarks the CRUD operations for metrics. Here are the&lt;br /&gt;
results, compared to the benchmarks I did against Gnocchi 2.&lt;/p&gt;
&lt;p&gt;Without surprises (but with great pleasure), everything is between 13% and 26% faster. Those operations mostly consist of SQL operations for the backend and serialization on the API – nothing heavy.&lt;/p&gt;
&lt;h3&gt;Sending and getting measures&lt;/h3&gt;
&lt;p&gt;Writing measures is still the hottest topic! How fast can you push things into that time series database and how efficient it is at retrieving those?&lt;/p&gt;
&lt;p&gt;Gnocchi has been supporting various batching methods for a while, and here the tested one is the simplest case, i.e., batching for one metric at a time.&lt;/p&gt;
&lt;p&gt;I think the chart talks for itself. With Redis as a driver, I attained almost &lt;strong&gt;1 million measures per second&lt;/strong&gt;. I did not find a suitable tool to report performances with a payload bigger than 5000 points, so I stopped at that. Those results are inline with what &lt;a href=&quot;https://medium.com/@gord.chung/gnocchi-4-introspective-a83055e99776&quot;&gt;Gordon Chung measured recently on Gnocchi 4&lt;/a&gt; – though he achieved &lt;strong&gt;1.3 million measures per second&lt;/strong&gt; with his bigger hardware!&lt;/p&gt;
&lt;p&gt;These are performances using HTTP as a protocol – with all its overhead and JSON serialization going on. Gnocchi does not implement any custom protocol so far because we never had any requirement for more performances. However, that would certainly be a good path to follow for anyone wanting to go even faster.&lt;/p&gt;
&lt;p&gt;Reading metrics is 54% faster here again. You can retrieve up to 400 000 measures per second (around 150 Mbit/s of data). That means you can retrieve a metric with a whole year of measures with a one-minute aggregate in 1.3 seconds. More realistically, you can retrieve the last 24 hours of data with a one minute precision for 280 metrics in just one second. That is more data you could ever fit on your graph dashboard!&lt;/p&gt;
&lt;p&gt;Most of the time is spent serializing points in JSON – again, a different retrieving mechanism could be envisioned to achieve even higher performances.&lt;/p&gt;
&lt;h3&gt;&lt;em&gt;Metricd&lt;/em&gt; speed&lt;/h3&gt;
&lt;p&gt;I did not benchmark myself metricd speed, as &lt;a href=&quot;https://medium.com/@gord.chung/gnocchi-4-introspective-a83055e99776&quot;&gt;Gordon wrote a complete report in the meantime&lt;/a&gt;. Gnocchi 4 multiplies the processing speed from Gnocchi 2 by a factor of 2.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-metricd-speed.png&quot; alt=&quot;Chart showing Gnocchi metricd processing speed improvements from version 2 to 4&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This speed is quite impressive and allows Gnocchi to ingest and pre-compute considerable amount of data in a short time span. Some of the changes Gordon tested here are not yet released and will be part of the next minor release (4.1).&lt;/p&gt;
&lt;p&gt;Being that efficient means that with only 1 CPU, Gnocchi can process (data aggregation) roughly 700 measures per second. If you have 70 servers and gather 10 metrics per server every second, Gnocchi can process them without any delay.&lt;/p&gt;
&lt;p&gt;If you scale back your polling to one minute instead of one second (the most common scenario) and use a single computer with 12 cores, that means Gnocchi can &lt;strong&gt;aggregate the metrics from 50 400 servers with only one server&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Not that bad.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Our processing engine is getting now really mature. Hundreds of deployments are now using it for production purpose of gathering metrics. The recent improvements made for Gnocchi 4 are a compelling argument for users to upgrade, and we are pretty proud of our work! We still have a few ideas on how to improve some corner cases, but the general use case is getting well covered. Adding to that the native horizontal capability that Gnocchi provides since day one, it is getting hard to find a time series database that has those features with this level of performance (but of course I&apos;m biased, haha).&lt;/p&gt;
&lt;p&gt;And if you have any questions, feel free to shoot them in the comment section. 😉&lt;/p&gt;
&lt;p&gt;$(function () { var chartColors = { red: &apos;rgb(234, 18, 51)&apos;, orange: &apos;rgb(255, 159, 64)&apos;, yellow: &apos;rgb(255, 205, 86)&apos;, green: &apos;rgb(75, 192, 192)&apos;, blue: &apos;rgb(46, 106, 234)&apos;, purple: &apos;rgb(153, 102, 255)&apos;, grey: &apos;rgb(201, 203, 207)&apos; }; var color = Chart.helpers.color; var ctx = $(&quot;#metric_crud&quot;).get(0).getContext(&quot;2d&quot;); var metric_crud = new Chart(ctx, { type: &apos;bar&apos;, data: { labels: [&quot;Create&quot;, &quot;Read&quot;, &quot;Delete&quot;], datasets: [ { label: &quot;Gnocchi 2&quot;, backgroundColor: color(chartColors.blue).alpha(0.6).rgbString(), borderColor: chartColors.blue, borderWidth: 1, data: [1300, 670, 524] }, { label: &quot;Gnocchi 4&quot;, backgroundColor: color(chartColors.red).alpha(0.6).rgbString(), borderColor: chartColors.red, borderWidth: 1, data: [1473, 843, 708] } ] }, options: { responsive: true, legend: { position: &apos;top&apos;, }, scales: { yAxes: [ { ticks: { beginAtZero: true } } ] }, title: { display: true, text: &apos;CRUD operations&apos; } }}); var ctx = $(&quot;#metric_measures&quot;).get(0).getContext(&quot;2d&quot;); var metric_measures = new Chart(ctx, { type: &apos;bar&apos;, data: { labels: [&quot;1&quot;, &quot;10&quot;, &quot;100&quot;, &quot;500&quot;, &quot;1000&quot;, &quot;2000&quot;, &quot;3000&quot;, &quot;4000&quot;, &quot;5000&quot;], datasets: [ { label: &quot;Gnocchi 2 (file)&quot;, backgroundColor: color(chartColors.blue).alpha(0.6).rgbString(), borderColor: chartColors.blue, borderWidth: 1, data: [624, 6000, 45000, 98000, 113000, 121000, 123000, 125000, 122000] }, { label: &quot;Gnocchi 4 (file)&quot;, backgroundColor: color(chartColors.red).alpha(0.6).rgbString(), borderColor: chartColors.red, borderWidth: 1, data: [1 * 754.26, 10 * 770.16, 100 * 583.53, 500 * 522.88, 1000 * 406.38, 2000 * 273.03, 3000 * 215.11, 4000 * 185.08, 5000 * 176.11] }, { label: &quot;Gnocchi 4 (Redis)&quot;, backgroundColor: color(chartColors.purple).alpha(0.6).rgbString(), borderColor: chartColors.purple, borderWidth: 1, data: [1 * 674, 10 * 782.34, 100 * 600, 500 * 533.66, 1000 * 405.38, 2000 * 282, 3000 * 223, 4000 * 195, 5000 * 185.41] } ] }, options: { responsive: true, legend: { position: &apos;top&apos;, }, scales: { yAxes: [ { scaleLabel: { display: true, labelString: &quot;Measures per second&quot; }, ticks: { beginAtZero: true } } ], xAxes: [ { scaleLabel: { display: true, labelString: &quot;Measures per request&quot; }, } ] }, title: { display: true, text: &apos;Measures writing&apos; } }}); var ctx = $(&quot;#metric_measures_get&quot;).get(0).getContext(&quot;2d&quot;); var metric_measures_get = new Chart(ctx, { type: &apos;bar&apos;, data: { labels: [&quot;Get measures for metric&quot;], datasets: [ { label: &quot;Gnocchi 2 (file)&quot;, backgroundColor: color(chartColors.blue).alpha(0.6).rgbString(), borderColor: chartColors.blue, borderWidth: 1, data: [260000] }, { label: &quot;Gnocchi 4 (file)&quot;, backgroundColor: color(chartColors.red).alpha(0.6).rgbString(), borderColor: chartColors.red, borderWidth: 1, data: [46.43 * 8640] } ] }, options: { responsive: true, legend: { position: &apos;top&apos;, }, scales: { yAxes: [ { scaleLabel: { display: true, labelString: &quot;Measures per second&quot; }, ticks: { beginAtZero: true } } ] }, title: { display: true, text: &apos;Measures reading&apos; } }}); });&lt;/p&gt;
</content:encoded></item><item><title>Attending PyCon FR 2017</title><link>https://julien.danjou.info/blog/pyconfr-announce/</link><guid isPermaLink="true">https://julien.danjou.info/blog/pyconfr-announce/</guid><description>The French edition of the annual Python conference, PyCon FR, will happen in Toulouse from 21st to 24th September.</description><pubDate>Thu, 31 Aug 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The French edition of the annual Python conference, &lt;a href=&quot;https://pycon.fr/2017/&quot;&gt;PyCon FR&lt;/a&gt;, will happen in Toulouse from 21st to 24th September.&lt;/p&gt;
&lt;p&gt;I skipped the last few PyCon FR, but this year I will be back with a one-hour long talk entitled &quot;&lt;em&gt;Scalable and distributed applications in Python&lt;/em&gt;&quot;. It will take place on Saturday afternoon. I will lay out many topics that will be covered in the book I&apos;m working on, &lt;a href=&quot;http://scaling-python.com&quot;&gt;Scaling Python&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;The Thursday and Friday will be dedicated to development sprints. I will be there with my friend &lt;a href=&quot;https://blog.sileht.net/&quot;&gt;Mehdi&lt;/a&gt; running a session for &lt;a href=&quot;http://gnocchi.xyz&quot;&gt;Gnocchi&lt;/a&gt;! We&apos;ll spend time teaching new contributors how to use it or how to send love and patches to the project. If you&apos;re into Python and want to learn about timeseries management, it&apos;s an excellent occasion to join us for some fun. 😎&lt;/p&gt;
&lt;p&gt;To join the sprint and the conference, visit the &lt;a href=&quot;http://pyconfr.org&quot;&gt;PyCon FR website&lt;/a&gt; and &lt;a href=&quot;https://www.eventbrite.fr/e/billets-pycon-fr-2017-a-toulouse-37380880219&quot;&gt;register&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Gnocchi or Prometheus?</title><link>https://julien.danjou.info/blog/gnocchi-or-prometheus/</link><guid isPermaLink="true">https://julien.danjou.info/blog/gnocchi-or-prometheus/</guid><description>The realm of time series database keeps expanding those last years. Now and then a new contender appears from the fog. People keep asking me about the difference between Gnocchi and Prometheus.</description><pubDate>Wed, 30 Aug 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The realm of time series database keeps expanding those last years. Now and then a new contender appears from the fog. People keep asking me about the difference between &lt;a href=&quot;http://gnocchi.xyz&quot;&gt;Gnocchi&lt;/a&gt; and &lt;a href=&quot;http://prometheus.io&quot;&gt;Prometheus&lt;/a&gt;. It&apos;s time to content them.&lt;/p&gt;
&lt;p&gt;Gnocchi and Prometheus are two open source projects evolving in the same expertise area, time series handling. They both are licensed under the &lt;strong&gt;Apache 2.0 license&lt;/strong&gt; (see &lt;a href=&quot;https://github.com/gnocchixyz/gnocchi/blob/master/LICENSE&quot;&gt;Gnocchi license file&lt;/a&gt; and &lt;a href=&quot;https://github.com/prometheus/prometheus/blob/master/LICENSE&quot;&gt;Prometheus license file&lt;/a&gt;. And that&apos;s a good thing!&lt;/p&gt;
&lt;p&gt;Both Gnocchi and Prometheus offers a bunch of features. Here&apos;s a table summary of the differences between the features they both offer – or not.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Feature&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Prometheus&lt;/p&gt;
&lt;p&gt;Gnocchi&lt;/p&gt;
&lt;p&gt;Multi-tenant&lt;/p&gt;
&lt;p&gt;❌&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;User auth &amp;amp; ACL&lt;/p&gt;
&lt;p&gt;❌&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;Resource history&lt;/p&gt;
&lt;p&gt;❌&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;Metric polling&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;❌&lt;/p&gt;
&lt;p&gt;Highly available&lt;/p&gt;
&lt;p&gt;❌&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;Horizontal scalability&lt;/p&gt;
&lt;p&gt;❌&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;Alerting engine&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;❌&lt;/p&gt;
&lt;p&gt;Data compression&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;Pre-computed aggregation&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;Grafana support&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;collectd support&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;✓&lt;/p&gt;
&lt;p&gt;#comparison th, #comparison td + td { text-align: center; }&lt;/p&gt;
&lt;p&gt;There&apos;s a lot of overlap between the two projects, but there are also some major differences.&lt;/p&gt;
&lt;p&gt;First, Gnocchi does not try to solve the metric retrieval problem. Prometheus provides a pull mechanism and takes in charge of getting the measurements. Gnocchi developers estimate that they are plenty of tools already doing that and that work well, such as &lt;a href=&quot;http://collectd.org&quot;&gt;collectd&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/icon_siren.png&quot; alt=&quot;Alert siren icon&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Secondly, Prometheus offers an &lt;a href=&quot;https://prometheus.io/docs/alerting/overview/&quot;&gt;alerting engine&lt;/a&gt;, statically configured with&lt;br /&gt;
a YAML file. It is way better than Gnocchi which offers nothing in comparison – for now. Gnocchi developers &lt;a href=&quot;https://github.com/gnocchixyz/gnocchi/issues/71&quot;&gt;are discussing the feature&lt;/a&gt; and while it&apos;s not on the roadmap yet, it will happen. It will, however, leverage a REST API to be controlled, as it seems important to us to be able to define alerts&lt;br /&gt;
programmatically.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/icon_storage.png&quot; alt=&quot;Storage icon&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Then there is a bunch of features where Gnocchi shines compared to Prometheus, and it is the core of its function: storing metrics. Gnocchi has a great storage engine that supports many storage backends (plain&lt;br /&gt;
files, &lt;a href=&quot;https://docs.openstack.org/swift/latest/&quot;&gt;OpenStack Swift&lt;/a&gt;, &lt;a href=&quot;http://ceph.org&quot;&gt;Ceph&lt;/a&gt;…). It helps Gnocchi scaling horizontally and providing native high-availability, whereas Prometheus stays a single point of failure.&lt;/p&gt;
&lt;p&gt;Multi-tenant and authentication are also supported by Gnocchi, allowing a single instance to be shared by multiple accounts. System administrators do not commonly use this kind of feature, but applications developers usually need them.&lt;/p&gt;
&lt;p&gt;That brings me to the usage and querying of Prometheus and Gnocchi. Prometheus has its small DSL (referred to as &lt;a href=&quot;https://prometheus.io/docs/querying/basics/&quot;&gt;PromQL&lt;/a&gt;) whereas Gnocchi has a &lt;a href=&quot;http://gnocchi.xyz/rest.html&quot;&gt;fully featured REST API&lt;/a&gt; that tries to expose proper semantic. It does not seem there are major differences between the two in term of features.&lt;/p&gt;
&lt;p&gt;Both Prometheus and Gnocchi support aggregating values over time ranges on query time (&quot;give me the minimum value for every 5 minutes range over the last day&quot;). Gnocchi always aggregates metrics at writing time, and never at query time (unless doing it cross-metrics). This implies that Gnocchi needs a bit of CPU time at write time to pre-compute those aggregates, but it is blazingly fast at reading time as it has nothing to compute. Prometheus can do the same thing using &lt;a href=&quot;https://prometheus.io/docs/querying/rules/&quot;&gt;recording rules&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/icon_clock.png&quot; alt=&quot;Clock icon&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Prometheus has some limitations inherent to time series database designed around the notion of &quot;monitoring&quot;: they tend to compute everything relatively to &lt;code&gt;$NOW&lt;/code&gt;. For example, it seems impossible to inject data from the past. The timestamp for a value is the timestamp where Prometheus read that value. If Prometheus misses values for a few hours, don&apos;t think about importing it back.&lt;/p&gt;
&lt;p&gt;I&apos;m noting this here as it makes it harder to benchmark Prometheus for ingestion. You need tons of fake metrics to polls and build data. I did not find any reference of Prometheus performances online, though it is advertised to ingest &quot;millions of measures from thousands of sources&quot;.&lt;/p&gt;
&lt;p&gt;Query performances seem to vary on Prometheus, and I did not find any benchmark on that neither. Gnocchi leverages standard RDBMS (MySQL or PostgreSQL is supported) to query indexed data and the metrics retrieval is always &lt;em&gt;O(1)&lt;/em&gt;, making it &lt;strong&gt;always fast&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;If you look in different and older areas, there never has been only one HTTP server. Many people use Apache HTTP server, but you&apos;ll find plenty of users of nginx, Tomcat, HAProxy, Node.js or uwsgi which are also common options nowadays. Same goes for RDBMS if you look at PostgreSQL, MySQL and other databases solution, etc. There will never be a project winning all the market share.&lt;/p&gt;
&lt;p&gt;It seems to me that time series storage and management is also growing in this category. There will probably be various projects that will enjoy some popularity and growth. Every project addresses the time series problem space with a different view and different trade-offs. There might never be a single project solving all problems at once.&lt;/p&gt;
&lt;p&gt;Prometheus seems to be oriented toward monitoring of live systems. Gnocchi is oriented to highly available time series storage at massive scale. Not considering performances (I was not able to compare anyway), both have different tradeoffs in term of features, philosophy, and orientation. Depending on your use cases, one might be a better fit than the other.&lt;/p&gt;
</content:encoded></item><item><title>Using Gnocchi with Docker</title><link>https://julien.danjou.info/blog/using-gnocchi-with-docker/</link><guid isPermaLink="true">https://julien.danjou.info/blog/using-gnocchi-with-docker/</guid><description>I&apos;ve recently started to look into Docker to build images ready to be used with Gnocchi in it. I found it would be a great way to distribute a working instance of Gnocchi.</description><pubDate>Thu, 17 Aug 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve recently started to look into Docker to build images ready to be used with &lt;a href=&quot;http://gnocchi.xyz&quot;&gt;Gnocchi&lt;/a&gt; in it. I found it would be a great way to distribute a working instance of Gnocchi.&lt;/p&gt;
&lt;p&gt;To this end, we created the &lt;a href=&quot;https://github.com/gnocchixyz/gnocchi-docker&quot;&gt;gnocchi-docker&lt;/a&gt; repository on GitHub. It contains:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a 11 lines long (only!) &lt;a href=&quot;https://github.com/gnocchixyz/gnocchi-docker/blob/master/gnocchi/Dockerfile&quot;&gt;Dockerfile&lt;/a&gt; to build a Linux image containing Gnocchi;&lt;/li&gt;
&lt;li&gt;a &lt;a href=&quot;https://github.com/gnocchixyz/gnocchi-docker/tree/master/grafana&quot;&gt;Dockerfile&lt;/a&gt; to create a &lt;a href=&quot;https://grafana.com/&quot;&gt;Grafana&lt;/a&gt; image that will use Gnocchi as datasource (preconfigured);&lt;/li&gt;
&lt;li&gt;a &lt;a href=&quot;https://github.com/gnocchixyz/gnocchi-docker/blob/master/collectd/Dockerfile&quot;&gt;Dockerfile&lt;/a&gt; to create a &lt;a href=&quot;http://collectd.org&quot;&gt;collectd&lt;/a&gt; image that gather various metrics for your container in order to feed Gnocchi and have something to display in Grafana;&lt;/li&gt;
&lt;li&gt;a &lt;a href=&quot;https://github.com/gnocchixyz/gnocchi-docker/blob/master/docker-compose.yaml&quot;&gt;docker-compose file&lt;/a&gt; that orchestrates and runs those containers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you don&apos;t know &lt;a href=&quot;https://docs.docker.com/compose/&quot;&gt;docker-compose&lt;/a&gt;, it&apos;s a tool to define and run applications using multiple containers. This is very handy in our case, as we need to start a few services, and therefore a few containers, to have our whole stack running.&lt;/p&gt;
&lt;p&gt;If you just want to use and run Gnocchi in a snap using this, it&apos;s easy. First clone the repository:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ git clone https://github.com/gnocchixyz/gnocchi-docker.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, just ask docker-compose to start your stack of containers:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cd gnocchi-docker
$ docker-compose up
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On the first run, &lt;code&gt;docker-compose&lt;/code&gt; will build the various images (this should take only a few minutes) and then will start them.&lt;/p&gt;
&lt;p&gt;Once everything is started, you can connect to Grafana by typing the URL &lt;code&gt;http://&amp;lt;ip of your docker server&amp;gt;:3000&lt;/code&gt; in your browser and using &quot;admin&quot; as username and &quot;password&quot; as password. Just click on the dashboard entitled &quot;Gnocchi&quot; and wait a few minutes: you will see the chart being drawn in real time!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-docker-grafana-screenshot.png&quot; alt=&quot;gnocchi-docker-grafana-screenshot&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The data fed into Gnocchi come from the &lt;code&gt;collectd&lt;/code&gt; container, which gathers various metrics (CPU, network interface statistics, etc).&lt;/p&gt;
&lt;p&gt;You can then edit the docker files as you like to add new features or test your code. The files are also a good basis if you want to deploy Gnocchi in production running Docker!&lt;/p&gt;
&lt;p&gt;If you want to access and play with Gnocchi in command line, just install &lt;a href=&quot;https://pypi.python.org/pypi/gnocchiclient&quot;&gt;gnocchiclient&lt;/a&gt; and do the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ export GNOCCHI_ENDPOINT=http://`docker-machine ip`:8041
$ gnocchi resource list
+----------+----------+------------+---------+----------------------+------------+----------+----------------+--------------+---------+
| id       | type     | project_id | user_id | original_resource_id | started_at | ended_at | revision_start | revision_end | creator |
+----------+----------+------------+---------+----------------------+------------+----------+----------------+--------------+---------+
| c31e4adc | collectd | None       | None    | collectd:fake-phy-   | 2017-08-17 | None     | 2017-08-17T12: | None         | admin   |
| -2cff-5f |          |            |         | host-719acbad336c    | T12:20:27. |          | 20:27.643790+0 |              |         |
| 78-8206- |          |            |         |                      | 643778+00: |          | 0:00           |              |         |
| f5ca66e4 |          |            |         |                      | 00         |          |                |              |         |
| 6cce     |          |            |         |                      |            |          |                |              |         |
+----------+----------+------------+---------+----------------------+------------+----------+----------------+--------------+---------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can now have fun creating new resources and metrics!&lt;/p&gt;
&lt;p&gt;Feel free to contribute patches to &lt;a href=&quot;http://github.com/gnocchixyz/gnocchi-docker&quot;&gt;the GitHub project&lt;/a&gt; too, obviously!&lt;/p&gt;
</content:encoded></item><item><title>Easy Python logging with daiquiri</title><link>https://julien.danjou.info/blog/python-logging-easy-with-daiquiri/</link><guid isPermaLink="true">https://julien.danjou.info/blog/python-logging-easy-with-daiquiri/</guid><description>After more than 10 years of writing Python, there&apos;s something I always have been annoyed with: logging.</description><pubDate>Tue, 04 Jul 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;After more than 10 years of writing Python, there&apos;s something I always have&lt;br /&gt;
been annoyed with: logging.&lt;/p&gt;
&lt;p&gt;Don&apos;t read me wrong: I like the &lt;a href=&quot;https://docs.python.org/3/library/logging.html&quot;&gt;Python logging subsystem&lt;/a&gt;. It&apos;s easy to use and works like a charm in most cases. If you never used it, logging in Python turns down to be as simple as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import logging

logger = logging.getLogger()
logger.info(&quot;Something useful&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It could barely be easier. What annoys me is that if you run the example above, an error will happen. See by yourself:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; import logging
&amp;gt;&amp;gt;&amp;gt; logger = logging.getLogger()
&amp;gt;&amp;gt;&amp;gt; logger.error(&quot;Something useful&quot;)
No handlers could be found for logger &quot;root&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nothing is printed, except an error. No log file is created. Logging does not work &quot;by default&quot;. I hate it.&lt;/p&gt;
&lt;p&gt;Each time I write a new application, I need to remember how to set logging up. There&apos;s a full API, documented, that explains how to setup handlers, formatters, filters, or a record factory. And each time I need to dig into all that documentation to remember how to set some sane defaults (e.g. log to stderr in a format with a timestamp). I could use &lt;a href=&quot;https://docs.python.org/3/library/logging.html#logging.basicConfig&quot;&gt;&lt;code&gt;logging.basicConfig&lt;/code&gt;&lt;/a&gt;, but it&apos;s usually too basic (e.g. it does not print any timestamp).&lt;/p&gt;
&lt;p&gt;Each time I go down the rabbit-hole of tweaking logging.&lt;/p&gt;
&lt;h2&gt;Here comes daiquiri&lt;/h2&gt;
&lt;p&gt;I finally took some time recently to bootstrap a tiny library to do this job for me. It&apos;s named &lt;em&gt;daiquiri&lt;/em&gt;, and it does only one thing: configure the Python logging subsystem for modern Python applications.&lt;/p&gt;
&lt;p&gt;It&apos;s small and the 1.0.0 version I just released contains 228 lines of code and 79 lines of tests. That&apos;s it!&lt;/p&gt;
&lt;p&gt;Its promise is to setup a complete standard Python logging system with just one function call. Nothing more, nothing less. The interesting features are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Logs to stderr by default.&lt;/li&gt;
&lt;li&gt;Use colors if logging to a terminal.&lt;/li&gt;
&lt;li&gt;Support file logging.&lt;/li&gt;
&lt;li&gt;Use program name as the name of the logging file so providing just a directory for logging will work.&lt;/li&gt;
&lt;li&gt;Support syslog.&lt;/li&gt;
&lt;li&gt;Support journald.&lt;/li&gt;
&lt;li&gt;JSON output support.&lt;/li&gt;
&lt;li&gt;Support of arbitrary key/value context information providing.&lt;/li&gt;
&lt;li&gt;Capture the warnings emitted by the &lt;a href=&quot;https://docs.python.org/3/library/warnings.html&quot;&gt;&lt;code&gt;warnings&lt;/code&gt;&lt;/a&gt; module.&lt;/li&gt;
&lt;li&gt;Native logging of any exception.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And it&apos;s used by &lt;a href=&quot;http://gnocchi.xyz&quot;&gt;Gnocchi&lt;/a&gt; starting with version 4.0. That should say how long it&apos;s production ready, right? 😀&lt;/p&gt;
&lt;p&gt;Enough selling. Let&apos;s see how it looks by default!&lt;/p&gt;
&lt;h2&gt;Basic working&lt;/h2&gt;
&lt;p&gt;Here&apos;s the basic usage of daiquiri:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import daiquiri

daiquiri.setup()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I told you I want it to be simple. Just doing this is already doing a better job than &lt;code&gt;logging.basicConfig&lt;/code&gt;, since it&apos;ll do something useful by default:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; import daiquiri
&amp;gt;&amp;gt;&amp;gt; daiquiri.setup()
&amp;gt;&amp;gt;&amp;gt; logger = daiquiri.getLogger()
&amp;gt;&amp;gt;&amp;gt; logger.error(&quot;something wrong happened&quot;)
2017-07-04 18:03:04,929 [16876] ERROR root: something wrong happened
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It does print the message on &lt;code&gt;stderr&lt;/code&gt; using a useful formatting and a timestamp by default. Just what everybody wants, isn&apos;t it? If you run this on a terminal, the line will be printed in red as it is an error that is logged. Other colors will be used for different logging levels (green for debug, etc).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/daiquiri.png&quot; alt=&quot;daiquiri&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Better, daiquiri will log any exception in your program:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; import daiquiri
&amp;gt;&amp;gt;&amp;gt; daiquiri.setup()
&amp;gt;&amp;gt;&amp;gt; raise Exception(&quot;boom!&quot;)
2017-07-04 18:05:43,378 [16959] CRITICAL root: Traceback (most recent call last):
  File &quot;&amp;lt;stdin&amp;gt;&quot;, line 1, in &amp;lt;module&amp;gt;
Exception: boom!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As soon as an exception is uncaught, it&apos;ll be logged as a critical log message.&lt;/p&gt;
&lt;h2&gt;More advanced features&lt;/h2&gt;
&lt;p&gt;If you want to tweak the default output, you can pass some arguments to &lt;code&gt;daiquiri.setup&lt;/code&gt;. This function accepts a &lt;code&gt;outputs&lt;/code&gt; argument that must be an iterable of &lt;code&gt;daiquiri.Output&lt;/code&gt; objects. This is typically a list of &lt;code&gt;daiquiri.File&lt;/code&gt; object to log to a file, &lt;code&gt;daiquiri.Syslog&lt;/code&gt; to log to &lt;em&gt;syslog&lt;/em&gt; or &lt;code&gt;daiquiri.Stream&lt;/code&gt; to log to any stream (e.g. an opened file, &lt;code&gt;stdout&lt;/code&gt; or &lt;code&gt;stderr&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;If you want to log via &lt;code&gt;syslog&lt;/code&gt; but also to &lt;code&gt;stderr&lt;/code&gt;, here&apos;s what you&apos;ll have to do:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;daiquiri.setup(outputs=(
    daiquiri.output.Syslog(),
    daiquiri.output.STDERR,
))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want to log to a file, you can just specify a directory, daiquiri will guess the program name and creates the appropriate file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## If the program name is foobar-server then the logging will
## be done to /var/log/foobar-server.log
daiquiri.setup(outputs=(
     daiquiri.output.File(directory=&quot;/var/log&quot;),
))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Those examples might be too easy. So let&apos;s log to journald and also to a network server using JSON output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import socket
import daiquiri

## Let&apos;s connect to the server first
s = socket.socket()
## You can run a simple server in another terminal by typing `nc -l 2333`
s.connect((&quot;localhost&quot;, 2333))
f = s.makefile()

daiquiri.setup(outputs=(
     daiquiri.output.Journal(),
     daiquiri.output.Stream(f, formatter=daiquiri.formatter.JSON_FORMATTER),
))
daiquiri.getLogger().error(&quot;oops&quot;, somekey=42, anotherkey=&quot;foobar&quot;)
## Server will receive:
## {&quot;message&quot;: &quot;oops&quot;, &quot;somekey&quot;: 42, &quot;anotherkey&quot;: &quot;foobar&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can obviously extend it with your own formatter or outputs, the API is pretty simple. But the default should be usable for 99% of applications.&lt;/p&gt;
&lt;p&gt;Let me know what you think and feel free to pip install and git clone it! The library is available at &lt;a href=&quot;http://pypi.python.org/pypi/daiquiri&quot;&gt;PyPI&lt;/a&gt;, the source is on &lt;a href=&quot;https://github.com/jd/daiquiri&quot;&gt;GitHub&lt;/a&gt; and the &lt;a href=&quot;http://daiquiri.readthedocs.io/&quot;&gt;documentation is published online&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Gnocchi 4 is out</title><link>https://julien.danjou.info/blog/gnocchi-4-release/</link><guid isPermaLink="true">https://julien.danjou.info/blog/gnocchi-4-release/</guid><description>Finally! Four months ago we pushed the Gnocchi 3.1 release and here we are now, release the 4th major version of that timeseries database.  A lot happened in the last 4 months.First, as I already wrot</description><pubDate>Tue, 13 Jun 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Finally! Four months ago we pushed the Gnocchi 3.1 release and here we are now, release the 4th major version of that timeseries database.&lt;/p&gt;
&lt;p&gt;A lot happened in the last 4 months. &lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-logo.png&quot; alt=&quot;Gnocchi logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;First, as I already wrote about, &lt;a href=&quot;https://julien.danjou.info/blog/gnocchi-independence&quot;&gt;we moved to GitHub for hosting our project&lt;/a&gt;. This slowed down our development pace for a couple of weeks, but we&apos;re now almost back to normal! We were a bit sad to quit the great infrastructure that we used before, but it feels great to be hosted on a platform everyone knows about and is more straightforward to use.&lt;/p&gt;
&lt;p&gt;Second, we implemented some major changes that should improve performances &lt;em&gt;again&lt;/em&gt;. We tend to that in each release, I know, I know. As usual, the release notes contains most of &lt;a href=&quot;http://gnocchi.xyz/releasenotes/4.0.html&quot;&gt;the major changes we did and can be read online&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But I&apos;d like to talk about few here that I find very exciting. The work and performances tests that Alex Krzos did (and&lt;br /&gt;
&lt;a href=&quot;https://julien.danjou.info/blog/2017/openstack-summit-pike-boston-recap&quot;&gt;we presented during the last OpenStack Summit&lt;/a&gt;) was of a great help for inspiration on where to improve performances.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;http://redis.io&quot;&gt;Redis&lt;/a&gt;! We added a Redis driver which can store incoming measures and metric archives. Obviously, it&apos;s more meant for incoming measures. Remember, in Gnocchi 3.1 we split the storage driver into two parts: the incoming measure storage and the archive storage. Since you can use two different drivers for those different functions, with Gnocchi 4.0 you can use Redis to store your incoming measures in a very fast temporary storage service and then &lt;em&gt;metricd&lt;/em&gt; will process them and store the results in your favorite scalable storage such as &lt;a href=&quot;http://ceph.com&quot;&gt;Ceph&lt;/a&gt;, where it&apos;s mostly read.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sacks! We rewrote the entire scheduling mechanism for &lt;em&gt;metricd&lt;/em&gt;. It now uses several &quot;sacks&quot; to store incoming measures in a distributed manner, instead of the previous one-sack-only storage for those incoming data. A hashring is then used to spread the processing workload on all the running &lt;em&gt;metricd&lt;/em&gt; daemon. Faster, simpler and more efficient scheduling should happen with this version!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;S3! We fixed the S3 driver. It was a nice proof-of-concept in 3.1 and now it should work. For real.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&apos;s mostly it. The rest of the changes are bug fixes there and there and some performance improvement, but this should be enough to get you excited to try it out.&lt;/p&gt;
&lt;p&gt;Come and join us on &lt;a href=&quot;http://github.com/gnocchixyz&quot;&gt;GitHub&lt;/a&gt;! Star us, and stay tuned for some more awesome news around metrics.&lt;/p&gt;
</content:encoded></item><item><title>Sending GitHub pull-request from your shell</title><link>https://julien.danjou.info/blog/git-pull-request-command-line-tool/</link><guid isPermaLink="true">https://julien.danjou.info/blog/git-pull-request-command-line-tool/</guid><description>I&apos;ve always been frustrated by the GitHub workflow. A while back I wrote how Gerrit workflow was superior to GitHub pull-request system.</description><pubDate>Wed, 24 May 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve always been frustrated by the GitHub workflow. A while back I&lt;br /&gt;
wrote &lt;a href=&quot;https://julien.danjou.info/blog/2013/rant-about-github-pull-request-workflow-implementation&quot;&gt;how Gerrit workflow was superior&lt;/a&gt; to GitHub pull-request system. But it seems that GitHub listened and they improved the pull-request system these last years to include reviews, and different workflow implementation, e.g. requiring continuous integration tests to pass before merging a patch.&lt;/p&gt;
&lt;p&gt;All those improvements great helped the Gnocchi team to consider moving to GitHub when leaving OpenStack. Our first days have been great and I cannot say we miss Gerrit much for now.&lt;/p&gt;
&lt;p&gt;The only tool that I loved and miss is &lt;a href=&quot;https://docs.openstack.org/infra/git-review/&quot;&gt;git-review&lt;/a&gt;. It allows pushing a branch of update easily to Gerrit.&lt;/p&gt;
&lt;p&gt;Unfortunately, in the GitHub world, things are different. To send a pull-request you have to execute a few steps which are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Clone the target repository&lt;/li&gt;
&lt;li&gt;Push your local branch to your repository&lt;/li&gt;
&lt;li&gt;Create a pull-request from your pushed local branch to the target branch&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you want to update later your pull-request, you either have to push new commits to your branch or, more often, edit your patches and force push your branch to your forked repository so you can ask for a new review of your pull-request.&lt;/p&gt;
&lt;p&gt;I&apos;m way too lazy to do all of that by hand, so I had a tool for a few years that I used based on &lt;a href=&quot;https://hub.github.com/&quot;&gt;hub&lt;/a&gt;, a command-line tool that interacts with &lt;a href=&quot;https://developer.github.com/&quot;&gt;GitHub API&lt;/a&gt;. Unfortunately, it was pretty simple and did not have all the feature I wanted.&lt;/p&gt;
&lt;p&gt;Which pushed me to write my own tool, humbly entitled &lt;a href=&quot;https://github.com/jd/git-pull-request&quot;&gt;git-pull-request&lt;/a&gt;. It allows to send a pull-request to any GitHub project just after you just cloned it. So there&apos;s no need to manually fork the repository, send branches, etc.&lt;/p&gt;
&lt;p&gt;Once you created a branch and committed to it, just run &lt;code&gt;git pull-request&lt;/code&gt; and everything we&apos;ll be done for you automatically.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## First pull-request creation
$ git clone https://github.com/gnocchixyz/gnocchi.git
$ cd gnocchi
$ git checkout -b somefeature
&amp;lt;edit files&amp;gt;
$ git commit -a -m &apos;I did some changes&apos;
$ git pull-request
Forked repository: https://github.com/jd/gnocchi
Force-pushing branch `somefeature&apos; to remote `github&apos;
Counting objects: 5, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (5/5), 562 bytes | 0 bytes/s, done.
Total 5 (delta 3), reused 0 (delta 0)
remote: Resolving deltas: 100% (3/3), completed with 3 local objects.
To https://github.com/jd/gnocchi.git
 + 73a733f7...1be2bf29 somefeature -&amp;gt; somefeature (forced update)
Pull-request created: https://github.com/gnocchixyz/gnocchi/pull/33
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you need to update your pull-request with new patches, just edit your branch and call &lt;code&gt;git pull-request&lt;/code&gt; again. It&apos;ll re-push your branch and will not create a pull-request if one already exists.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;edit some more files&amp;gt;
$ git commit --amend -a
$ git pull-request
Forked repository: https://github.com/jd/gnocchi
Force-pushing branch `somefeature to remote `github&apos;
Counting objects: 5, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (5/5), 562 bytes | 0 bytes/s, done.
Total 5 (delta 3), reused 0 (delta 0)
remote: Resolving deltas: 100% (3/3), completed with 3 local objects.
To https://github.com/jd/gnocchi.git
 + 73a733f7...1be2bf29 somefeature -&amp;gt; somefeature (forced update)
Pull-request already exists at: https://github.com/gnocchixyz/gnocchi/pull/33
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This tool was definitely the missing piece to smooth my GitHub workflow, so I&apos;m glad I took some time to write it. I hope you&apos;ll enjoy it and will send me awesome pull-requests, so go &lt;a href=&quot;https://github.com/jd/git-pull-request&quot;&gt;check it out&lt;/a&gt;. This program is written in Python and uses the GitHub API.&lt;/p&gt;
&lt;p&gt;And feel free to request new fancy features!&lt;/p&gt;
</content:encoded></item><item><title>OpenStack Summit Boston 2017 recap</title><link>https://julien.danjou.info/blog/openstack-summit-pike-boston-recap/</link><guid isPermaLink="true">https://julien.danjou.info/blog/openstack-summit-pike-boston-recap/</guid><description>The first OpenStack Summit of 2017 was last week, in Boston, MA, USA. I was able to attend as I&apos;ve been selected to give 3 talks, to help for a hands-on and to animate an on-boarding session.</description><pubDate>Mon, 15 May 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;https://www.openstack.org/summit/boston-2017/&quot;&gt;first OpenStack Summit of 2017&lt;/a&gt; was last week, in Boston, MA, USA. I was able to attend as I&apos;ve been selected to give 3 talks, to help for a hands-on and to animate an on-boarding session. This made sure I was a bit busy every day, which was good.&lt;/p&gt;
&lt;p&gt;This is the first summit to happen since the new &lt;a href=&quot;https://www.openstack.org/ptg/&quot;&gt;Project Team Gathering (PTG)&lt;/a&gt; happened last February. I was unable to attend this first PTG back then, as there was no way to justify my presence there. The OpenStack Telemetry team that I lead is pretty small. People don&apos;t really need to talk to each other face to face to discuss: therefore we decided to not ask to be present during the last PTG event.&lt;/p&gt;
&lt;p&gt;The Telemetry on-boarding session that I organized with my fellow developer Gordon Chung on Tuesday had only 3 people showing up to ask a few questions about Telemetry. The session lasted 15 minutes on 90 planned. We shared that session with &lt;a href=&quot;https://wiki.openstack.org/wiki/CloudKitty&quot;&gt;CloudKitty&lt;/a&gt;, for which nobody showed up for. When you think about it, this was really disappointing but did not come as a surprise.&lt;/p&gt;
&lt;p&gt;First, the amount of company engaging developers into OpenStack has shrunk drastically during the last year. Secondly, since there&apos;s now another event (the PTG) twice a year, it seems pretty clear that every developer will not be able to attend all the 4 events every year, creating dispersion in the community.&lt;/p&gt;
&lt;p&gt;I personally was glad to attend the Summit rather than the PTG, as it is more valuable to meet operators and users than developers to gather feedback. However, meeting everyone at the same time would be great, especially for smaller teams. The PTG scattered some teams to a point that many of developers of those lineups won&apos;t go to either the PTG nor the OpenStack. As a consequence, I won&apos;t have any meeting point in the future with many of my fellow developers around OpenStack. I warned the Technical Committee last year about this when it was decided to reorganize the events. I&apos;m glad to be right but I&apos;m a bit sad that the Foundation did not listen.&lt;/p&gt;
&lt;p&gt;Though all the projects I work on tend to follow &lt;a href=&quot;https://julien.danjou.info/blog/foss-projects-management-bad-practice&quot;&gt;the good practice I wrote last year&lt;/a&gt;. Therefore I cannot say that it has huge consequences on the projects I work on. It&apos;s a loss as it makes it harder to reach users and operators for some of us. It also reduces our occasion for social interaction, which was a great benefit. But it will not prevent us from building great software anyway!&lt;/p&gt;
&lt;p&gt;The few other sessions of &lt;em&gt;&lt;a href=&quot;https://wiki.openstack.org/wiki/Forum&quot;&gt;The Forum&lt;/a&gt;&lt;/em&gt; (the space dedicated to developers during the Summit) that I attended discussed various technical things, and some sessions were pretty empty. I wonder if it was a lack of interest of people or if people were unable to travel to discuss those items. Anyhow, at this stage I am not sure it would have really mattered: this has been my 9th OpenStack Summit and many of the subjects discussed already have been discussed multiple time with barely any change since. Talk is cheap. Furthermore, most of the discussion were not made by stakeholders of the various projects involved, but by people on the side, or by members of the Technical Committee. There is just unfortunately too much of wishful thinking.&lt;/p&gt;
&lt;p&gt;On the talk side, my presentation with Alex Krzos entitled &lt;em&gt;Telemetry and the 10,000 instances&lt;/em&gt; went pretty well. We demonstrated what how we tested the performance of the telemetry stack.&lt;/p&gt;
&lt;p&gt;Same goes for my hands-on with the CloudKitty developers, where we managed to explain how Ceilometer, Gnocchi, and CloudKitty were able to work with each other to create nice billing reports. The last day was concluded with my talk on collectd and Gnocchi with Emma, which was short and to the point.&lt;/p&gt;
&lt;p&gt;My final talk was about the status and roadmap of the OpenStack Telemetry team where I tried to explain how the Telemetry works and what we might do (or not) in the next cycles. It was pretty short as we barely have a roadmap, the project having 3 developers doing 80% of the work.&lt;/p&gt;
&lt;p&gt;I was also able to catch up with Nubeliu about their Gnocchi usage. They &lt;a href=&quot;https://www.youtube.com/watch?v=Hlt3UwsvgjU&quot;&gt;presented a nice demo of the cloud monitoring solution&lt;/a&gt; they build on top of Gnocchi. They completely understood how to use Gnocchi to store a large number of metrics at scale and how to leverage the API to render what&apos;s happening in your infrastructure. It is pretty amazing.&lt;/p&gt;
&lt;p&gt;While I missed the energy and the drive that the design session used to have in the first summits, it has been a pretty good summit. I was especially happy to be able to discuss OpenStack Telemetry and Gnocchi. The feedback I gathered was tremendous and terrific and I&apos;m looking forward to the work we&apos;ll achieve in the next months!&lt;/p&gt;
</content:encoded></item><item><title>Gnocchi independence</title><link>https://julien.danjou.info/blog/gnocchi-independence/</link><guid isPermaLink="true">https://julien.danjou.info/blog/gnocchi-independence/</guid><description>Three years have passed since Gnocchi started. After being incubated inside OpenStack, the project is now going independent.</description><pubDate>Sat, 06 May 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Three years have passed since I started working on &lt;a href=&quot;http://gnocchi.xyz&quot;&gt;Gnocchi&lt;/a&gt;. It&apos;s amazing to gaze at the path we wandered on.&lt;/p&gt;
&lt;p&gt;During all this time, Gnocchi has been &quot;incubated&quot; inside OpenStack. It has been created there and it grew with the rest of the ecosystem. But Gnocchi (developers) always stuck to some strange principles: autonomy and independence from the other OpenStack projects. This actually made the project a bit unpopular sometimes inside OpenStack, being stamped as some kind of &lt;em&gt;rebel&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I&apos;ve spent the last years asserting that each project inside OpenStack should seek towards living its own life. It is a key success for any open source project to be able to be used in any context, not only the one it has been built for. Having to use large bundles of projects together is not a good user story. I wish OpenStack will be a set of more autonomous building blocks.&lt;/p&gt;
&lt;p&gt;One of the most used project by people not using an entire OpenStack installation has been &lt;a href=&quot;https://launchpad.net/swift&quot;&gt;Swift&lt;/a&gt;. That was possible because Swift always tried to be autonomous and to not depend on any other service. It is able to leverage external services but it can also work without any. And I feel that Swift is the most successful project if you measure that success by being used by people having zero knowledge about OpenStack.&lt;/p&gt;
&lt;p&gt;With the move toward the &lt;em&gt;Big Tent&lt;/em&gt;, it struck me that the OpenStack Foundation will end up as some sort of an Apache Foundation. And I am pretty sure nobody forces you to use the &lt;a href=&quot;https://httpd.apache.org/&quot;&gt;Apache HTTP server&lt;/a&gt; if you want to use e.g. &lt;a href=&quot;http://lucene.apache.org/&quot;&gt;Lucene&lt;/a&gt; or &lt;a href=&quot;http://hbase.apache.org/&quot;&gt;HBase&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Being part of OpenStack for Gnocchi has been a great advantage at the beginning of the project. The infrastructure provided is awesome. The support we had from the community was great. The Gerrit workflow suited us well.&lt;/p&gt;
&lt;p&gt;But unfortunately, now that the project is getting more and more mature, many of the requirements of being an OpenStack project has become a real burden. The various processes forced by OpenStack is hurting the development pace. The contribution workflow based around Gerrit and &lt;a href=&quot;https://launchpad.net&quot;&gt;Launchpad&lt;/a&gt; is too complicated for most external contributors and therefore prevents new users to participate to the development.&lt;br /&gt;
Worse, the bad image or reputation that OpenStack carries in certain situation or communities is preventing Gnocchi to be evaluated and, maybe, used.&lt;/p&gt;
&lt;p&gt;I think that many of those negative aspects are finally taken into account by the OpenStack Technical Committee, as can be seen in the &lt;a href=&quot;https://review.openstack.org/#/c/453262/&quot;&gt;proposed vision of 2 years from now for OpenStack&lt;/a&gt;.&lt;br /&gt;
Better late than never.&lt;/p&gt;
&lt;p&gt;So after spending a lot of time weighing the pros and the cons, we, Gnocchi&lt;br /&gt;
contributors, &lt;a href=&quot;http://lists.openstack.org/pipermail/openstack-dev/2017-March/114300.html&quot;&gt;finally decided to move Gnocchi out of OpenStack&lt;/a&gt;.&lt;br /&gt;
We started to move the project to a brand new &lt;a href=&quot;https://github.com/gnocchixyz&quot;&gt;Gnocchi organization on GitHub&lt;/a&gt;. At the time of this writing, only the main gnocchi repository is missing and should be moved soon after the OpenStack Summit happening next week.&lt;/p&gt;
&lt;p&gt;We also used that opportunity to make usage of the new Gnocchi logo, courtesy of my friend Thierry Ung!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-logo.png&quot; alt=&quot;Gnocchi logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We&apos;ll see how everything will turn out and if the project will gain more traction, as we hope. This will not change the consumption of Gnocchi made by projects such as &lt;a href=&quot;http://launchpad.net/ceilometer&quot;&gt;Ceilometer&lt;/a&gt;. and the project aims to remain a good friend of OpenStack. 😀&lt;/p&gt;
</content:encoded></item><item><title>Python never gives up: the tenacity library</title><link>https://julien.danjou.info/blog/python-tenacity/</link><guid isPermaLink="true">https://julien.danjou.info/blog/python-tenacity/</guid><description>A couple of years ago, I wrote about the Python retrying library . This library was designed to retry the execution of a task when a failure occurred.  I started to spread usage of this library in var</description><pubDate>Thu, 02 Mar 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A couple of years ago, I &lt;a href=&quot;https://julien.danjou.info/blog/python-retrying&quot;&gt;wrote about the Python &lt;em&gt;retrying&lt;/em&gt; library&lt;/a&gt;. This library was designed to retry the execution of a task when a failure occurred.&lt;/p&gt;
&lt;p&gt;I started to spread usage of this library in various projects, such as &lt;a href=&quot;http://gnocchi.xyz&quot;&gt;Gnocchi&lt;/a&gt;, these last years. Unfortunately, it started to get very hard to contribute and send patches to the upstream &lt;em&gt;retrying&lt;/em&gt; project. I spent several months trying to work with the original author. But after a while, I had to come to the conclusion that I would be unable to fix bugs and enhance it at the pace I would like to. Therefore, I had to take a difficult decision and decided to fork the library.&lt;/p&gt;
&lt;h2&gt;Here comes &lt;em&gt;tenacity&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;I picked a new name and rewrote parts of the API of &lt;em&gt;retrying&lt;/em&gt; that were not working correctly or were too complicated. I also fixed bugs with the help of Joshua, and named this new library &lt;em&gt;tenacity&lt;/em&gt;. It works in the same manner as &lt;em&gt;retrying&lt;/em&gt; does, except that it is written in a more functional way and offers some nifty new features.&lt;/p&gt;
&lt;h2&gt;Basic usage&lt;/h2&gt;
&lt;p&gt;The basic usage is to use it as a decorator:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import tenacity

@tenacity.retry
def do_something_and_retry_on_any_exception():
    pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will make the function &lt;code&gt;do_something_and_retry_on_any_exception&lt;/code&gt; be called over and over again until it stops raising an exception. It would have been hard to design anything simpler. Obviously, this is a pretty rare case, as one usually wants to e.g. wait some time between retries. For that, &lt;em&gt;tenacity&lt;/em&gt; offers a large panel of waiting methods:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import tenacity

@tenacity.retry(wait=tenacity.wait_fixed(1))
def do_something_and_retry():
    do_something()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or a simple exponential back-off method can be used instead:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import tenacity

@tenacity.retry(wait=tenacity.wait_exponential())
def do_something_and_retry():
    do_something()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Combination&lt;/h2&gt;
&lt;p&gt;What is especially interesting with &lt;em&gt;tenacity&lt;/em&gt;, is that you can easily combine several methods. For example, you can combine &lt;code&gt;tenacity.wait.wait_random&lt;/code&gt; with &lt;code&gt;tenacity.wait.wait_fixed&lt;/code&gt; to wait a number of seconds defined in an interval:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import tenacity

@tenacity.retry(wait=tenacity.wait_fixed(10) + wait.wait_random(0, 3))
def do_something_and_retry():
    do_something()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will make the function being retried wait randomly between 10 and 13 seconds before trying again.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;tenacity&lt;/em&gt; offers more customization, such as retrying on some exceptions only. You can retry every second to execute the function only if the exception raised by &lt;code&gt;do_something&lt;/code&gt; is an instance of &lt;code&gt;IOError&lt;/code&gt;, e.g. a network communication error.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import tenacity

@tenacity.retry(wait=tenacity.wait_fixed(1),
                retry=tenacity.retry_if_exception_type(IOError))
def do_something_and_retry():
    do_something()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can combine several condition easily by using the &lt;code&gt;|&lt;/code&gt; or &lt;code&gt;&amp;amp;&lt;/code&gt; binary operators. They are used to make the code retry if an &lt;code&gt;IOError&lt;/code&gt; exception is raised, or if no result is returned. Also, a stop condition is added with the &lt;code&gt;stop&lt;/code&gt; keyword arguments. It allows to specify a condition unrelated to the function result of exception to stop, such as a number of attemps or a delay.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import tenacity

@tenacity.retry(wait=tenacity.wait_fixed(1),
                stop=tenacity.stop_after_delay(60),
                retry=(tenacity.retry_if_exception_type(IOError) |
                       tenacity.retry_if_result(lambda result: result == None))
def do_something_and_retry():
    do_something()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The functional approach of &lt;em&gt;tenacity&lt;/em&gt; makes it easy and clean to combine a lot of condition for various use cases with simple binary operators.&lt;/p&gt;
&lt;h2&gt;Standalone usage&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;tenacity&lt;/em&gt; can also be used without decorator by using the object &lt;code&gt;Retrying&lt;/code&gt;, that implements its main behaviour, and usig its &lt;code&gt;call&lt;/code&gt; method. This allows to call any function with different retry conditions, or to retry any piece of code that do not use the decorator at all – like code from an external library.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import tenacity

r = tenacity.Retrying(
    wait=tenacity.wait_fixed(1),
    retry=tenacity.retry_if_exception_type(IOError))
r.call(do_something)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This also allows you to re-use that object without creating one new each time, saving some memory!&lt;/p&gt;
&lt;p&gt;I hope you&apos;ll like it and will find it some use. Feel free to fork it, report bug or ask for new features on &lt;a href=&quot;https://github.com/jd/tenacity&quot;&gt;its GitHub&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;If you want to learn more about retrying strategy and how to handle failure, there&apos;s even more in &lt;a href=&quot;https://scaling-python.com&quot;&gt;Scaling Python&lt;/a&gt;. Check it out!&lt;/p&gt;
</content:encoded></item><item><title>Scalable metrics storage: Gnocchi on Amazon Web Services</title><link>https://julien.danjou.info/blog/metrics-on-amazon-with-gnocchi-s3-driver/</link><guid isPermaLink="true">https://julien.danjou.info/blog/metrics-on-amazon-with-gnocchi-s3-driver/</guid><description>As I wrote a few weeks ago in my post about Gnocchi 3.1 being released, one of the new feature available in this version it the S3 driver.</description><pubDate>Wed, 22 Feb 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As I wrote a few weeks ago in my &lt;a href=&quot;https://julien.danjou.info/blog/gnocchi-3-1-unleashed&quot;&gt;post about Gnocchi 3.1 being released&lt;/a&gt;, one of the new feature available in this version it the &lt;a href=&quot;https://aws.amazon.com/s3/&quot;&gt;S3&lt;/a&gt; driver. Today I would like to show you how easy it is to use it and store millions of metrics into the simple, durable and massively scalable object storage provided&lt;br /&gt;
by &lt;a href=&quot;https://aws.amazon.com/&quot;&gt;Amazon Web Services&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;p&gt;The installation of Gnocchi for this use case is not different than&lt;br /&gt;
the &lt;a href=&quot;http://gnocchi.xyz/install.html&quot;&gt;standard installation procedure described in the documentation&lt;/a&gt;. Simply install Gnocchi from &lt;a href=&quot;http://pypi.python.org&quot;&gt;PyPI&lt;/a&gt; using the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ pip install gnocchi[s3,postgresql] gnocchiclient
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will install Gnocchi with the dependencies for the S3 and PostgreSQL drivers and the command-line interface to talk with Gnocchi.&lt;/p&gt;
&lt;h2&gt;Configuring Amazon RDS&lt;/h2&gt;
&lt;p&gt;Since you need a SQL database for the indexer, the easiest way to get started is to create a database on &lt;a href=&quot;https://console.aws.amazon.com/rds/&quot;&gt;Amazon RDS&lt;/a&gt;. You can create a managed &lt;a href=&quot;http://postgresql.org&quot;&gt;PostgreSQL&lt;/a&gt; database instance in just a few clicks.&lt;/p&gt;
&lt;p&gt;Once you&apos;re on the homepage of &lt;a href=&quot;https://console.aws.amazon.com/rds/&quot;&gt;Amazon RDS&lt;/a&gt;, pick PostgreSQL as a&lt;br /&gt;
database:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-rds-postgresql.png&quot; alt=&quot;gnocchi-rds-postgresql&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You can then configure your PostgreSQL instance: I&apos;ve picked a dev/test instance with the basic options available within the RDS Free Tier, but you can pick whatever you think is needed for your production use. Set a username and a password and note them for later: we&apos;ll need them to configure Gnocchi.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-rds-postgresql-conf.png&quot; alt=&quot;gnocchi-rds-postgresql-conf&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The next step is to configure the database in details. Just set the database name to &quot;gnocchi&quot; and leave the other options to their default values (I&apos;m lazy).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-rds-postgresql-details.png&quot; alt=&quot;gnocchi-rds-postgresql-details&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After a few minutes, your instance should be created and running. Note down the endpoint. In this case, my instance is &lt;code&gt;gnocchi.cywagbaxpert.us-east-1.rds.amazonaws.com&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-rds-postgresql-running.png&quot; alt=&quot;gnocchi-rds-postgresql-running&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Configuring Gnocchi for S3 access&lt;/h2&gt;
&lt;p&gt;In order to give Gnocchi an access to S3, you need to create access keys. The easiest way to create them is to go to &lt;a href=&quot;https://console.aws.amazon.com/iam&quot;&gt;IAM&lt;/a&gt; in your AWS console, pick a user with S3 access and click on the big gray button named &quot;Create access key&quot;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-iam-create-keys.png&quot; alt=&quot;gnocchi-iam-create-keys&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Once you do that, you&apos;ll get the &lt;em&gt;access key id&lt;/em&gt; and &lt;em&gt;secret access key&lt;/em&gt;. Note them down, we will need these later.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-iam-get-keys.png&quot; alt=&quot;gnocchi-iam-get-keys&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Creating &lt;code&gt;gnocchi.conf&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Now is time to create the &lt;code&gt;gnocchi.conf&lt;/code&gt; file. You can place it in &lt;code&gt;/etc/gnocchi&lt;/code&gt; if you want to deploy it system-wide, or in any other directory and add the &lt;code&gt;--config-file&lt;/code&gt; option to each Gnocchi command..&lt;/p&gt;
&lt;p&gt;Here are the values that you should retrieve and write in the configuration file:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;indexer.url&lt;/code&gt;: the PostgreSQL RDS instance endpoint and credentials (see above) to set into&lt;/li&gt;
&lt;li&gt;&lt;code&gt;storage.s3_endpoint_url&lt;/code&gt;: the S3 endpoint URL – that depends on the region you want to use and &lt;a href=&quot;http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region&quot;&gt;they are listed here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;storage.s3_region_name&lt;/code&gt;: the S3 region name matching the endpoint you picked.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;storage.s3_access_key_id&lt;/code&gt; and &lt;code&gt;storage.s3_secret_acess_key&lt;/code&gt;: your AWS access key id and secret access key.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Your &lt;code&gt;gnocchi.conf&lt;/code&gt; file should then look like that:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[indexer]
url = postgresql://gnocchi:gn0cch1rul3z@gnocchi.cywagbaxpert.us-east-1.rds.amazonaws.com:5432/gnocchi

[storage]
driver = s3
s3_endpoint_url = https://s3-eu-west-1.amazonaws.com
s3_region_name = eu-west-1
s3_access_key_id = &amp;lt;you access key id&amp;gt;
s3_secret_access_key = &amp;lt;your secret access key&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once that&apos;s done, you can run &lt;code&gt;gnocchi-upgrade&lt;/code&gt; in order to initialize Gnocchi indexer (PostgreSQL) and storage (S3):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ gnocchi-upgrade --config-file gnocchi.conf
2017-02-07 15:35:52.491 3660 INFO gnocchi.cli [-] Upgrading indexer &amp;lt;gnocchi.indexer.sqlalchemy.SQLAlchemyIndexer object at 0x108221950&amp;gt;
2017-02-07 15:36:04.127 3660 INFO gnocchi.cli [-] Upgrading storage &amp;lt;gnocchi.storage.s3.S3Storage object at 0x10ca943d0&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you can run the API endpoint using the test endpoint &lt;code&gt;gnocchi-api&lt;/code&gt; and specifying its default port 8041:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ gnocchi-api --port 8041 -- --config-file gnocchi.conf
2017-02-07 15:53:06.823 6290 INFO gnocchi.rest.app [-] WSGI config used: /Users/jd/Source/gnocchi/gnocchi/rest/api-paste.ini
********************************************************************************
STARTING test server gnocchi.rest.app.build_wsgi_app
Available at http://127.0.0.1:8041/
DANGER! For testing only, do not use in production
********************************************************************************
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The best way to run Gnocchi API is to use &lt;a href=&quot;http://gnocchi.xyz/master/running.html#running-api-as-a-wsgi-application&quot;&gt;uwsgi as documented&lt;/a&gt;, but in this case, using the testing daemon &lt;code&gt;gnocchi-api&lt;/code&gt; is good enough.&lt;/p&gt;
&lt;p&gt;Finally, in another terminal, you can start the &lt;code&gt;gnocchi-metricd&lt;/code&gt; daemon that will process metrics in background:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ gnocchi-metricd --config-file gnocchi.conf
2017-02-07 15:52:41.416 6262 INFO gnocchi.cli [-] 0 measurements bundles across 0 metrics wait to be processed.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once everything is running, you can use Gnocchi&apos;s client to query it and check that everything is OK. The backlog should be empty at this stage, obviously.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ gnocchi status
+-----------------------------------------------------+-------+
| Field                                               | Value |
+-----------------------------------------------------+-------+
| storage/number of metric having measures to process | 0     |
| storage/total number of measures to process         | 0     |
+-----------------------------------------------------+-------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Gnocchi is ready to be used!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ # Create a generic resource &quot;foobar&quot; with a metric named &quot;visitor&quot;
$ gnocchi resource create foobar -n visitor
+-----------------------+-----------------------------------------------+
| Field                 | Value                                         |
+-----------------------+-----------------------------------------------+
| created_by_project_id |                                               |
| created_by_user_id    | admin                                         |
| creator               | admin                                         |
| ended_at              | None                                          |
| id                    | b4d568e4-7af1-5aec-ac3f-9c09fa3685a9          |
| metrics               | visitor: 05f45876-1a69-4a64-8575-03eea5b79407 |
| original_resource_id  | foobar                                        |
| project_id            | None                                          |
| revision_end          | None                                          |
| revision_start        | 2017-02-07T14:54:54.417447+00:00              |
| started_at            | 2017-02-07T14:54:54.417414+00:00              |
| type                  | generic                                       |
| user_id               | None                                          |
+-----------------------+-----------------------------------------------+

## Send the number of visitor at 2 different timestamps
$ gnocchi measures add --resource-id foobar -m 2017-02-07T15:56@23 visitor
$ gnocchi measures add --resource-id foobar -m 2017-02-07T15:57@42 visitor

## Check the average number of visitor
## (the --refresh option is given to be sure the measure are processed)
$ gnocchi measures show --resource-id foobar visitor --refresh
+---------------------------+-------------+-------+
| timestamp                 | granularity | value |
+---------------------------+-------------+-------+
| 2017-02-07T15:55:00+00:00 |       300.0 |  32.5 |
+---------------------------+-------------+-------+

## Now shows the minimum number of visitor
$ gnocchi measures show --aggregation min --resource-id foobar visitor
+---------------------------+-------------+-------+
| timestamp                 | granularity | value |
+---------------------------+-------------+-------+
| 2017-02-07T15:55:00+00:00 |       300.0 |  23.0 |
+---------------------------+-------------+-------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And voilà! You&apos;re ready to store millions of metrics and measures on your Amazon Web Services cloud platform. I hope you&apos;ll enjoy it and feel free to ask any question in the comment section or by reaching me directly!&lt;/p&gt;
</content:encoded></item><item><title>Sending your collectd metrics to Gnocchi</title><link>https://julien.danjou.info/blog/gnocchi-collectd-setup/</link><guid isPermaLink="true">https://julien.danjou.info/blog/gnocchi-collectd-setup/</guid><description>Knowing that collectd is a daemon that collects system and applications metrics and that Gnocchi is a scalable timeseries database, it sounds like a good idea to combine them together.</description><pubDate>Thu, 16 Feb 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Knowing that &lt;a href=&quot;http://collectd.org/&quot;&gt;collectd&lt;/a&gt; is a daemon that collects system and applications metrics and that &lt;a href=&quot;http://gnocchi.xyz&quot;&gt;Gnocchi&lt;/a&gt; is a scalable timeseries database, it sounds like a good idea to combine them together. &lt;em&gt;Cherry on the cake&lt;/em&gt;: you can easily draw charts using &lt;a href=&quot;http://grafana.org&quot;&gt;Grafana&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While it&apos;s true that Gnocchi is well integrated with &lt;a href=&quot;http://openstack.org&quot;&gt;OpenStack&lt;/a&gt;, as it orginally comes from this ecosystem, it actually works standalone by default. Starting with the 3.1 version, it is now easy to send metrics to &lt;em&gt;Gnocchi&lt;/em&gt; using &lt;em&gt;collectd&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;p&gt;What we&apos;ll need to install to accomplish this task is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;collectd&lt;/li&gt;
&lt;li&gt;Gnocchi&lt;/li&gt;
&lt;li&gt;collectd-gnocchi&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;How you install them does not really matter. If they are packaged by your operating system, go ahead. For Gnocchi and collectd-gnocchi, you can also use &lt;em&gt;pip&lt;/em&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## pip install gnocchi[file,postgresql]
[…]
Successfully installed gnocchi-3.1.0
## pip install collectd-gnocchi
Collecting collectd-gnocchi
  Using cached collectd-gnocchi-1.0.1.tar.gz
[…]
Installing collected packages: collectd-gnocchi
  Running setup.py install for collectd-gnocchi ... done
Successfully installed collectd-gnocchi-1.0.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The detailed installation procedure for Gnocchi is &lt;a href=&quot;http://gnocchi.xyz/install.html#id1&quot;&gt;detailed in the documentation&lt;/a&gt;. It among other things explains which flavors are available – here I picked PostgreSQL and the file driver to store the metrics.&lt;/p&gt;
&lt;h2&gt;Configuration&lt;/h2&gt;
&lt;h3&gt;Gnocchi&lt;/h3&gt;
&lt;p&gt;Gnocchi is simple to configure and is again &lt;a href=&quot;http://gnocchi.xyz/configuration.html&quot;&gt;documented&lt;/a&gt;. The default configuration file is &lt;code&gt;/etc/gnocchi/gnocchi.conf&lt;/code&gt; – you can generate it with &lt;code&gt;gnocchi-config-generator&lt;/code&gt; if needed. However, it also possible to specify another configuration file by appending the &lt;code&gt;--config-file&lt;/code&gt; option to any command line&lt;/p&gt;
&lt;p&gt;In Gnocchi&apos;s configuration file, you need to set the &lt;code&gt;indexer.url&lt;/code&gt; configuration option to point an existing PostgreSQL database and set &lt;code&gt;storage.file_basepath&lt;/code&gt; to an existing directory to store your metrics (the default is &lt;code&gt;/var/lib/gnocchi&lt;/code&gt;). That gives something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[indexer]
url = postgresql://root:p4assw0rd@localhost/gnocchi

[storage]
file_basepath = /var/lib/gnocchi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once done, just run the &lt;code&gt;gnocchi-upgrade&lt;/code&gt; command to initialize the index and storage.&lt;/p&gt;
&lt;h3&gt;collectd&lt;/h3&gt;
&lt;p&gt;Collectd provides a default configuration file that loads a bunch of plugin by default, that will meter all sort of metrics on your computer. You can check the &lt;a href=&quot;http://collectd.org/documentation.shtml&quot;&gt;documentation&lt;/a&gt; online to see how to disable or enable plugins.&lt;/p&gt;
&lt;p&gt;As the &lt;em&gt;collectd-gnocchi&lt;/em&gt; plugin is written in Python, you&apos;ll need to enable the Python plugin and load the &lt;em&gt;collectd-gnocchi&lt;/em&gt; module:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LoadPlugin python

&amp;lt;Plugin python&amp;gt;
  Import &quot;collectd_gnocchi&quot;
  &amp;lt;Module collectd_gnocchi&amp;gt;
      endpoint &quot;http://localhost:8041&quot;
  &amp;lt;/Module&amp;gt;
&amp;lt;/Plugin&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is enough to enable the storage of metrics in Gnocchi.&lt;/p&gt;
&lt;h2&gt;Running the daemons&lt;/h2&gt;
&lt;p&gt;Once everything is configured, you can launch &lt;code&gt;gnocchi-metricd&lt;/code&gt; and the &lt;code&gt;gnocchi-api&lt;/code&gt; daemon:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ gnocchi-metricd
2017-01-26 15:22:49.018 15971 INFO gnocchi.cli [-] 0 measurements bundles
across 0 metrics wait to be processed.
[…]
## In another terminal
$ gnocchi-api --port 8041
[…]
STARTING test server gnocchi.rest.app.build_wsgi_app
Available at http://127.0.0.1:8041/
[…]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&apos;s not recommended to run Gnocchi using Gnocchi API (as&lt;br /&gt;
&lt;a href=&quot;http://gnocchi.xyz/running.html#running-as-a-wsgi-application&quot;&gt;written in the documentation&lt;/a&gt;): using &lt;a href=&quot;https://uwsgi-docs.readthedocs.io/&quot;&gt;uwsgi&lt;/a&gt; is a better option. However for rapid testing, the &lt;code&gt;gnocchi-api&lt;/code&gt; daemon is good enough.&lt;/p&gt;
&lt;p&gt;Once that&apos;s done, you can start &lt;code&gt;collectd&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ collectd
## Or to run in foreground with a different configuration file:
## $ collectd -C collectd.conf -f
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have any problem launchding &lt;em&gt;colllectd&lt;/em&gt;, check syslog for more information: there might be an issue loading a module or plugin.&lt;/p&gt;
&lt;p&gt;If no error are printed, then everythin&apos;s working fine and you soon should see &lt;em&gt;gnocchi-api&lt;/em&gt; printing some requests such as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;127.0.0.1 - - [26/Jan/2017 15:27:03] &quot;POST /v1/resource/collectd HTTP/1.1&quot; 409 113
127.0.0.1 - - [26/Jan/2017 15:27:03] &quot;POST /v1/batch/resources/metrics/measures?create_metrics=True HTTP/1.1&quot; 400 91
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Enjoying the result&lt;/h2&gt;
&lt;p&gt;Once everything runs, you can access your newly created resources and metric by using the &lt;a href=&quot;http://pypi.python.org/pypi/gnocchiclient&quot;&gt;gnocchiclient&lt;/a&gt;. It should have been installed as a dependency of &lt;em&gt;collectd_gnocchi&lt;/em&gt;, but you can also install it manually using &lt;code&gt;pip install gnocchiclient&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you need to specify a different endpoint you can use the &lt;code&gt;--endpoint&lt;/code&gt; option (which default to &lt;a href=&quot;http://localhost:8041&quot;&gt;http://localhost:8041&lt;/a&gt;). Do not hesitate to check the &lt;code&gt;--help&lt;/code&gt; option for more information.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ gnocchi resource list --details
+---------------+----------+------------+---------+----------------------+---------------+----------+----------------+--------------+---------+-----------+
| id            | type     | project_id | user_id | original_resource_id | started_at    | ended_at | revision_start | revision_end | creator | host      |
+---------------+----------+------------+---------+----------------------+---------------+----------+----------------+--------------+---------+-----------+
| dd245138-00c7 | collectd | None       | None    | dd245138-00c7-5bdc-  | 2017-01-26T14 | None     | 2017-01-26T14: | None         | admin   | localhost |
| -5bdc-94f8-26 |          |            |         | 94f8-263e236812f7    | :21:02.297466 |          | 21:02.297483+0 |              |         |           |
| 3e236812f7    |          |            |         |                      | +00:00        |          | 0:00           |              |         |           |
+---------------+----------+------------+---------+----------------------+---------------+----------+----------------+--------------+---------+-----------+
$ gnocchi resource show collectd:localhost
+-----------------------+-----------------------------------------------------------------------+
| Field                 | Value                                                                 |
+-----------------------+-----------------------------------------------------------------------+
| created_by_project_id |                                                                       |
| created_by_user_id    | admin                                                                 |
| creator               | admin                                                                 |
| ended_at              | None                                                                  |
| host                  | localhost                                                             |
| id                    | dd245138-00c7-5bdc-94f8-263e236812f7                                  |
| metrics               | interface-en0@if_errors-0: 5d60f224-2e9e-4247-b415-64d567cf5866       |
|                       | interface-en0@if_errors-1: 1df8b08b-555a-4cab-9186-f9b79a814b03       |
|                       | interface-en0@if_octets-0: 491b7517-7219-4a04-bdb6-934d3bacb482       |
|                       | interface-en0@if_octets-1: 8b5264b8-03f3-4aba-a7f8-3cd4b559e162       |
|                       | interface-en0@if_packets-0: 12efc12b-2538-45e7-aa66-f8b9960b5fa3      |
|                       | interface-en0@if_packets-1: 39377ff7-06e8-454a-a22a-942c8f2bca56      |
|                       | interface-en1@if_errors-0: c3c7e9fc-f486-4d0c-9d36-55cea855596a       |
|                       | interface-en1@if_errors-1: a90f1bec-3a60-4f58-a1d1-b3c09dce4359       |
|                       | interface-en1@if_octets-0: c1ee8c75-95bf-4096-8055-8c0c4ec8cd47       |
|                       | interface-en1@if_octets-1: cbb90a94-e133-4deb-ac10-3f37770e32f0       |
|                       | interface-en1@if_packets-0: ac93b1b9-da71-4876-96aa-76067b35c6c9      |
|                       | interface-en1@if_packets-1: 2f8528b2-12ae-4c4d-bec7-8cc987e7487b      |
|                       | interface-en2@if_errors-0: ddcf7203-4c49-400b-9320-9d3e0a63c6d5       |
|                       | interface-en2@if_errors-1: b249ea42-01ad-4742-9452-2c834010df71       |
|                       | interface-en2@if_octets-0: 8c23013a-604e-40bf-a07a-e2dc4fc5cbd7       |
|                       | interface-en2@if_octets-1: 806c1452-0607-4b56-b184-c4ffd48f52c0       |
|                       | interface-en2@if_packets-0: c5bc6103-6313-4b8b-997d-01930d1d8af4      |
|                       | interface-en2@if_packets-1: 478ae87e-e56b-44e4-83b0-ed28d99ed280      |
|                       | load@load-0: 5db2248d-2dca-401e-b2e2-bbaee23b623e                     |
|                       | load@load-1: 6f74ac93-78fd-4a74-a47e-d2add487a30f                     |
|                       | load@load-2: 1897aca1-356e-4791-907f-512e516992b5                     |
|                       | memory@memory-active-0: 83944a85-9c84-4fe4-b471-1a6cf8dce858          |
|                       | memory@memory-free-0: 0ccc7cfa-26a5-4441-a15f-9ebb2aa82c6d            |
|                       | memory@memory-inactive-0: 63736026-94c4-47c5-8d6f-a9d89d65025b        |
|                       | memory@memory-wired-0: b7217fd6-2cdc-4efd-b1a8-a1edd52eaa2e           |
| original_resource_id  | dd245138-00c7-5bdc-94f8-263e236812f7                                  |
| project_id            | None                                                                  |
| revision_end          | None                                                                  |
| revision_start        | 2017-01-26T14:21:02.297483+00:00                                      |
| started_at            | 2017-01-26T14:21:02.297466+00:00                                      |
| type                  | collectd                                                              |
| user_id               | None                                                                  |
+-----------------------+-----------------------------------------------------------------------+
% gnocchi metric show -r collectd:localhost load@load-0
+------------------------------------+-----------------------------------------------------------------------+
| Field                              | Value                                                                 |
+------------------------------------+-----------------------------------------------------------------------+
| archive_policy/aggregation_methods | min, std, sum, median, mean, 95pct, count, max                        |
| archive_policy/back_window         | 0                                                                     |
| archive_policy/definition          | - timespan: 1:00:00, granularity: 0:05:00, points: 12                 |
|                                    | - timespan: 1 day, 0:00:00, granularity: 1:00:00, points: 24          |
|                                    | - timespan: 30 days, 0:00:00, granularity: 1 day, 0:00:00, points: 30 |
| archive_policy/name                | low                                                                   |
| created_by_project_id              |                                                                       |
| created_by_user_id                 | admin                                                                 |
| creator                            | admin                                                                 |
| id                                 | 5db2248d-2dca-401e-b2e2-bbaee23b623e                                  |
| name                               | load@load-0                                                           |
| resource/created_by_project_id     |                                                                       |
| resource/created_by_user_id        | admin                                                                 |
| resource/creator                   | admin                                                                 |
| resource/ended_at                  | None                                                                  |
| resource/id                        | dd245138-00c7-5bdc-94f8-263e236812f7                                  |
| resource/original_resource_id      | dd245138-00c7-5bdc-94f8-263e236812f7                                  |
| resource/project_id                | None                                                                  |
| resource/revision_end              | None                                                                  |
| resource/revision_start            | 2017-01-26T14:21:02.297483+00:00                                      |
| resource/started_at                | 2017-01-26T14:21:02.297466+00:00                                      |
| resource/type                      | collectd                                                              |
| resource/user_id                   | None                                                                  |
| unit                               | None                                                                  |
+------------------------------------+-----------------------------------------------------------------------+
$ gnocchi measures show -r collectd:localhost load@load-0
+---------------------------+-------------+--------------------+
| timestamp                 | granularity |              value |
+---------------------------+-------------+--------------------+
| 2017-01-26T00:00:00+00:00 |     86400.0 | 3.2705004391254193 |
| 2017-01-26T15:00:00+00:00 |      3600.0 | 3.2705004391254193 |
| 2017-01-26T15:00:00+00:00 |       300.0 | 2.6022800611413044 |
| 2017-01-26T15:05:00+00:00 |       300.0 |  3.561742940080275 |
| 2017-01-26T15:10:00+00:00 |       300.0 | 2.5605337960379466 |
| 2017-01-26T15:15:00+00:00 |       300.0 |  3.837517851142473 |
| 2017-01-26T15:20:00+00:00 |       300.0 | 3.9625948392427883 |
| 2017-01-26T15:25:00+00:00 |       300.0 | 3.2690042162698414 |
+---------------------------+-------------+--------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, the command line works smoothly and can show you any kind of metric reported by &lt;em&gt;collectd&lt;/em&gt;. In this case, it was just running on my laptop, but you can imagine it&apos;s easy enough to poll thousands of hosts with &lt;em&gt;collectd&lt;/em&gt; and &lt;em&gt;Gnocchi&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;Bonus: charting with Grafana&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://grafana.org&quot;&gt;Grafana&lt;/a&gt;, a charting software, has a plugin for &lt;em&gt;Gnocchi&lt;/em&gt; as &lt;a href=&quot;http://gnocchi.xyz/grafana.html&quot;&gt;detailed in the documentation&lt;/a&gt;. Once installed, you can just configure &lt;em&gt;Grafana&lt;/em&gt; to point to &lt;em&gt;Gnocchi&lt;/em&gt; this way:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/grafana-config-screen-gnocchi.png&quot; alt=&quot;Screenshot of Grafana configuration screen pointing to Gnocchi as data source&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You can then create a new dashboard by filling the forms as you wish. See this other screenshot for a nice example:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/grafana-gnocchi-load.png&quot; alt=&quot;Charts of my laptop&apos;s load average&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I hope everything is clear and easy enough. If you have any question, feel free to write something in the comment section!&lt;/p&gt;
</content:encoded></item><item><title>FOSDEM 2017, recap</title><link>https://julien.danjou.info/blog/fosdem-2017-recap/</link><guid isPermaLink="true">https://julien.danjou.info/blog/fosdem-2017-recap/</guid><description>Last week-end, I was in Brussels, Belgium for the 2017 edition of the FOSDEM, one of the greatest open source developer conference.</description><pubDate>Mon, 06 Feb 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/fosdem.png&quot; alt=&quot;FOSDEM 2017 conference logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Last week-end, I was in Brussels, Belgium for the 2017 edition of the &lt;a href=&quot;http://fosdem.org&quot;&gt;FOSDEM&lt;/a&gt;, one of the greatest open source developer conference.&lt;/p&gt;
&lt;p&gt;This year, I decided to propose a talk about &lt;a href=&quot;http://gnocchi.xyz&quot;&gt;Gnocchi&lt;/a&gt; which was accepted in the &lt;a href=&quot;https://fosdem.org/2017/schedule/track/python/&quot;&gt;Python devroom&lt;/a&gt;. The track was very well organized (thanks to &lt;a href=&quot;https://wirtel.be/&quot;&gt;Stéphane Wirtel&lt;/a&gt;) and I was able to present Gnocchi to a room full of Python developers!&lt;/p&gt;
&lt;p&gt;I&apos;ve explained why we created Gnocchi and how we did it, and finally briefly explained how to use it with the command-line interface or in a Python application using the &lt;a href=&quot;http://gnocchi.xyz/gnocchiclient&quot;&gt;SDK&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can check the slides below and [the video of the talk (&lt;a href=&quot;https://video.fosdem.org/2017/UD2.120/storing_metrics_gnocchi.mp4&quot;&gt;https://video.fosdem.org/2017/UD2.120/storing_metrics_gnocchi.mp4&lt;/a&gt;).&lt;/p&gt;
</content:encoded></item><item><title>Gnocchi 3.1 unleashed</title><link>https://julien.danjou.info/blog/gnocchi-3-1-unleashed/</link><guid isPermaLink="true">https://julien.danjou.info/blog/gnocchi-3-1-unleashed/</guid><description>It&apos;s always difficult to know when to release, and we really wanted to do it earlier. But it seems that each week more awesome work was being done in Gnocchi, so we kept delaying it while having no.</description><pubDate>Thu, 02 Feb 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It&apos;s always difficult to know when to release, and we really wanted to do it earlier. But it seems that each week more awesome work was being done in &lt;a href=&quot;http://gnocchi.xyz&quot;&gt;Gnocchi&lt;/a&gt;, so we kept delaying it while having no pressure to push it out.&lt;/p&gt;
&lt;p&gt;But now that the OpenStack cycle is finishing, even Gnocchi does not strictly follow it, it seemed to be a good time to cut the leash and leave this release be.&lt;/p&gt;
&lt;p&gt;There are again some major new changes coming from 3.0. The previous version 3.0 was tagged in October and had 90 changes merged from 13 authors since 2.2. This 3.1 version have 200 changes merged from 24 different authors. This is a great improvement of our contributor base and our rate of change – even if our delay to merge is very low. Once again, we pushed usage of release notes to document user visible changes, and &lt;a href=&quot;http://gnocchi.xyz/releasenotes/3.1.html&quot;&gt;they can be read online&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Therefore, I am going to summary quickly the major changes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The REST API authentication mechanism has been modularized. It&apos;s now simple to provide any authentication mechanism for Gnocchi as a plugin. The default is now a HTTP basic authentication mechanism that does not implement any kind of enforcement. The &lt;a href=&quot;http://docs.openstack.org/developer/keystone/&quot;&gt;Keystone&lt;/a&gt; authentication is still available, obviously.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Batching has been improved and can now create metrics on the fly, reducing the latency needed when pushing measures to non-existing metrics. This is leveraged by the &lt;a href=&quot;https://github.com/gnocchixyz/collectd-gnocchi&quot;&gt;collectd-gnoccchi&lt;/a&gt; plugin for example.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The performance of Carbonara based backend has been largely improved. This is not really listed as a change as it&apos;s not user-visible, but an amazing work of profiling and rewriting code from &lt;a href=&quot;http://pandas.pydata.org/&quot;&gt;Pandas&lt;/a&gt; to &lt;a href=&quot;http://www.numpy.org/&quot;&gt;NumPy&lt;/a&gt; has been done. While Pandas is very developer-friendly and generic, using NumPy directly offers way more performance and should decrease &lt;code&gt;gnocchi-metricd&lt;/code&gt; CPU usage by a large factor.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The storage has been split into two parts: the storage of incoming new measures to be processed, and the storage and archival of aggregated metrics. This allows to use e.g. file to store new measures being sent, and once processed store them into e.g. Ceph. Before that change, all the new measures had to go into Ceph. While there&apos;s no specific driver yet for incoming measures, it&apos;s easy to envision a driver for systems like &lt;a href=&quot;https://redis.io&quot;&gt;Redis&lt;/a&gt; or &lt;a href=&quot;https://memcached.org&quot;&gt;Memcached&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A new &lt;a href=&quot;https://aws.amazon.com/s3/&quot;&gt;Amazon S3&lt;/a&gt; driver has been merged. It works in the same way than the file or &lt;a href=&quot;http://docs.openstack.org/developer/swift/&quot;&gt;OpenStack Swift&lt;/a&gt; drivers.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-logo-old.jpg&quot; alt=&quot;Gnocchi logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I will write more about some of these new features in the upcoming weeks, as they are very interesting for Gnocchi&apos;s users.&lt;/p&gt;
&lt;p&gt;We are planning to run a scalability test and benchmarks using the &lt;a href=&quot;http://scalelab.redhat.com/&quot;&gt;ScaleLab&lt;/a&gt; in a few weeks if everything goes as planned. I will obviously share the result here, but we also submitted a&lt;br /&gt;
talk for the next &lt;a href=&quot;https://www.openstack.org/summit/boston-2017/&quot;&gt;OpenStack Summit in Boston&lt;/a&gt; to present the results of our scalability and performance tests – hoping the session will be accepted.&lt;/p&gt;
&lt;p&gt;I will also be talking about Gnocchi &lt;a href=&quot;https://fosdem.org/2017/schedule/event/storing_metrics_gnocchi/&quot;&gt;this Sunday at FOSDEM&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We don&apos;t have a very determined roadmap for Gnocchi during the next weeks. Sure we do have a few ideas on what we want to implement, but we are also very easily influenced by the requests of our user: therefore feel free to ask for anything!&lt;/p&gt;
</content:encoded></item><item><title>Scaling Python is on its way</title><link>https://julien.danjou.info/blog/announcing-scaling-python/</link><guid isPermaLink="true">https://julien.danjou.info/blog/announcing-scaling-python/</guid><description>My day-to-day activities are still evolving around the Python programming language, as I continue working on the OpenStack project as part of my job at Red Hat.</description><pubDate>Mon, 16 Jan 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;My day-to-day activities are still evolving around the &lt;a href=&quot;http://python.org&quot;&gt;Python&lt;/a&gt; programming language, as I continue working on the &lt;a href=&quot;http://openstack.org&quot;&gt;OpenStack&lt;/a&gt; project as part of my job at &lt;a href=&quot;http://redhat.com&quot;&gt;Red Hat&lt;/a&gt;. OpenStack is still the biggest Python project out there, and attract a lot of Python hackers.&lt;/p&gt;
&lt;p&gt;Those last few years, however, things have taken a different turn for me when I made the choice with my team to rework the telemetry stack architecture. We&lt;br /&gt;
decided to make a point of making it scale way beyond what has been done in the project so far.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://scaling-python.com&quot;&gt;&lt;img src=&quot;https://scaling-python.com/img/the-hacker-guide-to-scaling-python.png&quot; alt=&quot;Cover of The Hacker&apos;s Guide to Scaling Python&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I started to dig into a lot of different fields around Python. Topics you don&apos;t often look at when writing a simple and straight-forward application. It turns out that writing scalable applications in Python is not impossible, nor that difficult. There are a few hiccups to avoid, and various tools that can help, but it really is possible – without switching to another whole language, framework, or exotic tool set.&lt;/p&gt;
&lt;p&gt;Working on those projects seemed to me like a good opportunity to share with the rest of the world what I learned. Therefore, I decided to share my most recent knowledge addition around distributed and scalable Python application in a new book, entitled &lt;a href=&quot;https://scaling-python.com&quot;&gt;The Hacker&apos;s Guide to Scaling Python&lt;/a&gt; (or &lt;em&gt;Scaling Python&lt;/em&gt;, in short). The book should be released in a few months – fingers crossed.&lt;/p&gt;
&lt;p&gt;And as the book is still a work-in-progress, I&apos;ll be happy to hear any remark, subject, interrogation or topic idea you might have or any particular angle you would like me to take in this book (reply in the &lt;a href=&quot;#disqus_thread&quot;&gt;comments section&lt;/a&gt; or shoot me an &lt;a href=&quot;mailto:julien@danjou.info&quot;&gt;email&lt;/a&gt;). And if you&apos;d like to get be kept updated on this book advancement, you can subscribe in the following form or from the &lt;a href=&quot;https://scaling-python.com&quot;&gt;book homepage&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The adventure of working on my previous book, &lt;a href=&quot;https://thehackerguidetopython.com&quot;&gt;The Hacker&apos;s Guide to Python&lt;/a&gt;, has been so tremendous and the feedback so great, that I&apos;m looking forward releasing this new book later this year!&lt;/p&gt;
</content:encoded></item><item><title>Packaging Python software with pbr</title><link>https://julien.danjou.info/blog/packaging-python-with-pbr/</link><guid isPermaLink="true">https://julien.danjou.info/blog/packaging-python-with-pbr/</guid><description>Packaging Python has been a painful experience for long. The history of the various distribution that Python offered along the years is really bumpy, and both the user and developer experience has bee</description><pubDate>Mon, 02 Jan 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Packaging Python has been a painful experience for long. The history of the various distribution that Python offered along the years is really bumpy, and both the user and developer experience has been pretty bad.&lt;/p&gt;
&lt;p&gt;Fortunately, things improved a lot in the recent years, with the reconciliation of &lt;em&gt;&lt;a href=&quot;https://setuptools.readthedocs.io&quot;&gt;setuptools&lt;/a&gt;&lt;/em&gt; and &lt;em&gt;distribute&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Though in the context of the &lt;a href=&quot;http://openstack.org&quot;&gt;OpenStack&lt;/a&gt; project, a solution on top of &lt;em&gt;setuptools&lt;/em&gt; has been already started a while back. Its usage is now spread across a whole range of software and libraries.&lt;/p&gt;
&lt;p&gt;This project is called &lt;em&gt;&lt;a href=&quot;http://docs.openstack.org/developer/pbr/&quot;&gt;pbr&lt;/a&gt;&lt;/em&gt;, for &lt;em&gt;Python Build Reasonableness&lt;/em&gt;. Don&apos;t be afraid by the OpenStack colored themed of the documentation – it is a bad habit of OpenStack folks to not advertise their tooling in an agnostic fashion. The tool has no dependency with the cloud platform, and can be used painlessly with any package.&lt;/p&gt;
&lt;h2&gt;How it works&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;pbr&lt;/em&gt; takes inspiration from &lt;em&gt;distutils2&lt;/em&gt; (a now abandoned project) and uses a &lt;code&gt;setup.cfg&lt;/code&gt; file to describe the packager&apos;s intents. This is how a &lt;code&gt;setup.py&lt;/code&gt; using &lt;em&gt;pbr&lt;/em&gt; looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import setuptools

setuptools.setup(setup_requires=[&apos;pbr&apos;], pbr=True)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two lines of code – it&apos;s that simple. The actual metadata that the setup requires is stored in &lt;code&gt;setup.cfg&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[metadata]
name = foobar
author = Dave Null
author-email = foobar@example.org
summary = Package doing nifty stuff
license = MIT
description-file =
    README.rst
home-page = http://pypi.python.org/pypi/foobar
requires-python = &amp;gt;=2.6
classifier = 
    Development Status :: 4 - Beta
    Environment :: Console
    Intended Audience :: Developers
    Intended Audience :: Information Technology
    License :: OSI Approved :: Apache Software License
    Operating System :: OS Independent
    Programming Language :: Python

[files]
packages =
    foobar
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This syntax is way easier to write and read than the standard &lt;code&gt;setup.py&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;pbr&lt;/em&gt; also offers other features such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;automatic dependency installation based on &lt;code&gt;requirements.txt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;automatic documentation building and generation using Sphinx&lt;/li&gt;
&lt;li&gt;automatic generation of &lt;code&gt;AUTHORS&lt;/code&gt; and &lt;code&gt;ChangeLog&lt;/code&gt; files based on &lt;em&gt;git&lt;/em&gt; history&lt;/li&gt;
&lt;li&gt;automatic creation of the list of files to include using &lt;em&gt;git&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;version management based on &lt;em&gt;git&lt;/em&gt; tags&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of this comes with little to no effort on your part.&lt;/p&gt;
&lt;h2&gt;Using flavors&lt;/h2&gt;
&lt;p&gt;One of the feature that I use a lot, is the definition of flavors. It&apos;s not tied particularly to &lt;em&gt;pbr&lt;/em&gt; – it&apos;s actually provided by &lt;em&gt;setuptools&lt;/em&gt; and &lt;em&gt;pip&lt;/em&gt; themselves – but &lt;em&gt;pbr&lt;/em&gt; &lt;code&gt;setup.cfg&lt;/code&gt; file makes it easy to use.&lt;/p&gt;
&lt;p&gt;When distributing a software, it&apos;s common to have different drivers for it. For example, your project could support both PostgreSQL or MySQL – but nobody is going to use both at the same time. The usual trick to make it work is to add the needed library to the requirements list (e.g. &lt;code&gt;requirements.txt&lt;/code&gt;). The upside is that the software will work directly with either RDBMS, but the downside is that this will install both libraries, whereas only one is needed. Using flavors, you can specify different scenarios:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[extras]
postgresql =
    psycopg2
mysql =
    pymysql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When installing your package, the user can then just pick the right flavor by using &lt;em&gt;&lt;a href=&quot;https://pip.pypa.io/&quot;&gt;pip&lt;/a&gt;&lt;/em&gt; to install the package:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ pip install foobar[postgresql]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will install &lt;em&gt;foobar&lt;/em&gt;, all its dependencies listed in &lt;code&gt;requirements.txt&lt;/code&gt;, plus whatever dependencies are listed in the &lt;code&gt;[extras]&lt;/code&gt; section of &lt;code&gt;setup.cfg&lt;/code&gt; matching the flavor. You can also combine several flavors, e.g.:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ pip install foobar[postgresql,mysql]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;would install both flavors.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;pbr&lt;/em&gt; is well-maintained and in very active development, so if you have any plans to distribute your software, you should seriously consider including &lt;em&gt;pbr&lt;/em&gt; in those plans.&lt;/p&gt;
</content:encoded></item><item><title>Attending OpenStack Summit Ocata</title><link>https://julien.danjou.info/blog/openstack-summit-ocata-barcelona-review/</link><guid isPermaLink="true">https://julien.danjou.info/blog/openstack-summit-ocata-barcelona-review/</guid><description>For the last time in 2016, I flew out to the OpenStack Summit in Barcelona, where I had the chance to meet (again) a lot of my fellow OpenStack contributors there.</description><pubDate>Mon, 31 Oct 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;For the last time in 2016, I flew out to the &lt;a href=&quot;https://www.openstack.org/summit/barcelona-2016/&quot;&gt;OpenStack Summit in Barcelona&lt;/a&gt;, where I had the chance to meet (again) a lot of my fellow OpenStack contributors there.&lt;/p&gt;
&lt;h2&gt;How To Work Upstream with OpenStack&lt;/h2&gt;
&lt;p&gt;My week started by giving a talk about &lt;em&gt;How To Work Upstream with OpenStack&lt;/em&gt; where I explained, accompanied by Ryota and Ashiq, to the audience how to contribute upstream to OpenStack. It went well and was well received by the public – you can watch the video below or&lt;br /&gt;
download the slides.&lt;/p&gt;
&lt;h2&gt;Python 3 in telemetry projects&lt;/h2&gt;
&lt;p&gt;I&apos;ve attended a few interesting cross-project sessions, which helped me getting some prioritization for my work during the next few months.&lt;/p&gt;
&lt;p&gt;The Python 3 porting effort is blocked for a while in Nova and Swift for various (mostly non-technical) reasons, while almost all other projects are working correctly. On the other hand, we have committed the telemetry projects to be the first one to drop Python 2 support has soon as it is possible. The next steps are to be sure downstream is ready and enable functional testing in devstack with Python 3.&lt;/p&gt;
&lt;h2&gt;Ceilometer deprecation&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gordon-gnocchi-talk.jpg&quot; alt=&quot;gordon-gnocchi-talk&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The Ceilometer sessions were really interesting, are we mainly discussed deprecating and removing old crufts that are not or should not be used anymore. The main change will be the depreciation of the Ceilometer API. It has been clear for more than a year that &lt;a href=&quot;http://gnocchi.xyz&quot;&gt;Gnocchi&lt;/a&gt; is the way-to-go to store and provide access to metrics, but we failed at announcing wildly. A lot of the people I talked to during the summit were not aware that the Ceilometer API was not a good pick, and that Gnocchi was the now recommended storage backend. Bad communication from our side – but we are going to fix it as of now.&lt;/p&gt;
&lt;p&gt;We also committed to simplify the current architecture by removing the collector, which has now be made obsolete by the agent based architecture that was implemented during the last development cycles.&lt;/p&gt;
&lt;h2&gt;Aodh alarm timeout&lt;/h2&gt;
&lt;p&gt;We had a feature proposal for a while in Aodh that we postponed for too long already: having timeout triggered after not having seen some events. This seems to be a functionality requested by NFV users – something we want Aodh to cover.&lt;/p&gt;
&lt;p&gt;We spent some time discussing this feature, and now that we all have a clear understanding of the use case, we&apos;ll work on having a clear path to the implementation.&lt;/p&gt;
&lt;p&gt;I&apos;ve also attended a session with the &lt;a href=&quot;https://wiki.openstack.org/wiki/Vitrage&quot;&gt;Vitrage&lt;/a&gt; developers in order to discuss how we could work better together, as they rely on Aodh. It seems there might be some convergence in the future, which would be very welcome. Wait&apos;n see.&lt;/p&gt;
&lt;h2&gt;Gnocchi improvement, past and future&lt;/h2&gt;
&lt;p&gt;The Gnocchi session ran smoothly, and everyone seemed happy with the work we have done so far. We&apos;ve made some impressive improvement in Gnocchi 3.0 – as &lt;a href=&quot;https://julien.danjou.info/blog/2016/gnocchi-3.0-release&quot;&gt;I already covered previously&lt;/a&gt; – and Gordon Chung presented a short talk about the performance difference metered while working on this new version of Gnocchi:&lt;/p&gt;
&lt;p&gt;The return of the InfluxDB driver is on the table, as Sam Morrison proposed a patch for that while back. While it&apos;s not as fast and scalable as other drivers, it offers a good alternative for people having to use it.&lt;/p&gt;
&lt;p&gt;Leandro Reox presented how to do capacity planning using Ceilometer and Gnocchi, presenting the projects at the same time:&lt;/p&gt;
&lt;p&gt;It is pretty impressive to see what they achieved with this project, and I&apos;m looking forward to being able to check how it works inside.&lt;/p&gt;
&lt;h2&gt;PTG and beyond&lt;/h2&gt;
&lt;p&gt;The next meeting is supposed to be the new &lt;a href=&quot;https://www.openstack.org/ptg/&quot;&gt;OpenStack PTG&lt;/a&gt; in February in Atlanta, though we did not request any specific space there. While the team love seeing each other face-to-face every few months, we achieved to follow &lt;a href=&quot;https://julien.danjou.info/blog/foss-projects-management-bad-practice&quot;&gt;all of the guidelines I listed recently&lt;/a&gt; on good open source project management, meaning we are able to work very well asynchronously and remotely. There is no need to put hard requirements on people wanting to participate in our community. Nevertheless, I expect cross-projects discussions that will happen to still concern the OpenStack Telemetry projects.&lt;/p&gt;
&lt;p&gt;In the end, we&apos;re all very happy with our past and future roadmaps and I&apos;m looking forward to achieving our next big milestones with our amazing telemetry team!&lt;/p&gt;
</content:encoded></item><item><title>Running an open source and upstream oriented team in agile mode</title><link>https://julien.danjou.info/blog/opensource-upstream-team-agile/</link><guid isPermaLink="true">https://julien.danjou.info/blog/opensource-upstream-team-agile/</guid><description>For the last 3 years, I&apos;ve been working in the OpenStack Telemetry team at eNovance, and then at Red Hat. Our mission is to maintain the OpenStack Telemetry stack, both upstream and downstream (i.e.</description><pubDate>Tue, 18 Oct 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;For the last 3 years, I&apos;ve been working in the OpenStack Telemetry team at &lt;a href=&quot;http://enovance.com&quot;&gt;eNovance&lt;/a&gt;, and then at &lt;a href=&quot;http://redhat.com&quot;&gt;Red Hat&lt;/a&gt;. Our mission is to maintain the OpenStack Telemetry stack, both upstream and downstream (i.e. inside Red Hat products). Besides the technical challenges, the organization of the team always have played a major role in our accomplishments.&lt;/p&gt;
&lt;p&gt;Here, I&apos;d like to share some of my hindsight with you, faithful readers.&lt;/p&gt;
&lt;h2&gt;Meet the team&lt;/h2&gt;
&lt;p&gt;The team I work in changed a bit during those 3 years, but the core components always have been the same: a few software engineers, a QE engineer, a product owner, and an engineering manager. That meant the team size has been always been between 6 and 8 people.&lt;/p&gt;
&lt;p&gt;I cannot emphasize enough how team size is important. Not having more than 8 persons in a team fits with the &lt;a href=&quot;https://www.fastcompany.com/3037542/productivity-hack-of-the-week-the-two-pizza-approach-to-productive-teamwork&quot;&gt;two pizzas rule from Jeff Bezzos&lt;/a&gt;, which turned out to be key in our team composition.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/pizza-rule.jpg&quot; alt=&quot;pizza-rule&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The group dynamic that is applied by teams not bigger than this is excellent. It offers the possibility to know and connect with everyone – each time member has only up to 7 people to talk to on a daily basis, which means only 28 communication axis between people. Having a team of e.g. 16 people means 120 different links in your team. Double your team size, and multiply by 4 your communication overhead. My experience shows that the less communication axis you have in a team, the less overhead your will have and the swifter your team will be.&lt;/p&gt;
&lt;p&gt;All team members being remote workers, is is even more challenging to build relationship and bound. We had the opportunity to know each other during the OpenStack summit twice a year and doing regular video-conference via &lt;a href=&quot;http://hangout.google.com&quot;&gt;Google Hangout&lt;/a&gt; or &lt;a href=&quot;http://bluejeans.com&quot;&gt;BlueJeans&lt;/a&gt; really helped.&lt;/p&gt;
&lt;p&gt;The atmosphere you set-up in your team will also forge the outcome of your team work. Run your team with trust, peace and humor (remember I&apos;m on the team 🤣) and awesome things will happen. Run your team with fear, pressure, and finger-pointing, nothing good will happen.&lt;/p&gt;
&lt;p&gt;There&apos;s little chance that when a team is built, everyone will be on the same level. We were no exception, we had more and less experienced engineers. But the most experienced engineers took the time needed to invest and mentor the less experienced. That also helped to build trust and communication links between members of the team. And over the long run, everyone is getting more efficient: the less experienced engineers are getting better and the more experienced can delegate a lot of stuff to their fellows.&lt;/p&gt;
&lt;p&gt;Then they can chill or work on bigger stuff. Win-win.&lt;/p&gt;
&lt;p&gt;It&apos;s actually no more different than that the way you should run an open source team, as I already claimed in a &lt;a href=&quot;https://julien.danjou.info/blog/foss-projects-management-bad-practice&quot;&gt;previous article on FOSS projects management&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/everything-is-awesome.jpg&quot; alt=&quot;everything-is-awesome&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Practicing agility&lt;/h2&gt;
&lt;p&gt;I might be bad at practicing agility: contrary to many people, I don&apos;t see agility as a set of processes. I see that as a state of mind, as a team organization based on empowerment. No more, no less.&lt;/p&gt;
&lt;p&gt;And each time I meet people and explain that our team is &quot;agile&quot;, they start shivering, explaining how they hate sprints, daily stand-ups, scrum, and &lt;a href=&quot;https://en.wikipedia.org/wiki/Planning_poker&quot;&gt;planning poker&lt;/a&gt;, that this is all a waste of time and energy.&lt;/p&gt;
&lt;p&gt;Well, it turns out that you can be agile without all of that.&lt;/p&gt;
&lt;h3&gt;Planning poker&lt;/h3&gt;
&lt;p&gt;In our team, we tried at first to run 2-weeks sprints and used planning poker to schedule our &lt;em&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/User_story&quot;&gt;user stories&lt;/a&gt;&lt;/em&gt; from our product backlog (= todo list). It never worked as expected.&lt;/p&gt;
&lt;p&gt;First, most people had the feeling to lose their time because they already knew exactly what they were supposed to do. Having any doubt, they would have just gone and talked to the product owner or another fellow engineer.&lt;/p&gt;
&lt;p&gt;Secondly, some stories were really specialized and only one of the team member was able to understand it in details and evaluate it. So most of the time, a lot of the team members playing planning poker would just vote a random number based on the length of the explanation of the story teller. For example, if an engineer said &quot;I just need to change that flag in the configuration file&quot; then everyone would vote 1. If they started rambling for 5 minutes about &quot;how the configuration option is easy to switch, but that there might be other things to change at the same time, and things to check for impact bigger than expected, and code refactoring to do&quot;, then most people would just announce a score of 13 on that &lt;em&gt;story&lt;/em&gt;. Just because the guy talked for 3 minutes straight and everything sounded complicated and out of their scope.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/planningpoker.jpg&quot; alt=&quot;planningpoker&quot; /&gt;&lt;/p&gt;
&lt;p&gt;That meant that the poker score had no meaning to us. We never managed to have a number of points that we knew we could accomplish during a sprint (the &lt;em&gt;team velocity&lt;/em&gt; as they call it).&lt;/p&gt;
&lt;p&gt;The only benefit that we identified from planning poker, in our case, is that it forces people to keep sit down and communicate about a &lt;em&gt;user story&lt;/em&gt;. Though, it turned out that making people communicate was not a problem we needed to solve in our team, so we decided to stop doing that. But it can be a pretty good tool to make people talking to each other.&lt;/p&gt;
&lt;p&gt;Therefore, the 2-weeks sprint never made much sense as we were unable to schedule our work reliably. Furthermore, doing most of our daily job in open source communities, we were unable to schedule anything. When sending patches to an upstream project, you have no clue when they will be reviewed. What you know for sure, is that in order to maximize your code merge throughput with this high latency of code review, you need to parallelize your patch submission a lot. So as soon as you receive some feedback from your reviewers, you need to (almost) drop everything, rework your code and resubmit it.&lt;/p&gt;
&lt;p&gt;There&apos;s no need to explain what this does not absolutely work with a sprint approach. Most of the &lt;a href=&quot;https://en.wikipedia.org/wiki/Scrum_(software_development)&quot;&gt;scrum framework&lt;/a&gt; lays on the fact that you own workflow from top to bottom, which is far from being true when working in open source communities.&lt;/p&gt;
&lt;h3&gt;Daily stand-up meetings&lt;/h3&gt;
&lt;p&gt;We used to run a daily stand-up meeting every day, then every other day. Doing that remotely kills the stand-up part, obviously, so there is less guarantee the meeting will be short. Considering all team members are working remotely in different time zones, with some freedom to organize their schedule, it was very difficult to synchronize those meetings. With member spread from the US to Eastern Europe, the meeting was in the middle of the afternoon for me. I found it frustrating to have to stop my activities in the middle of every afternoon to chat with my team. We all know the cost of context switching to us, humans.&lt;/p&gt;
&lt;p&gt;So we drifted from our 10 minutes daily meeting to a one-hour weekly meeting with the whole team. It&apos;s way easier to synchronize for a large chunk of time once a week and to have this high-throughput communication channel.&lt;/p&gt;
&lt;h2&gt;Our (own) agile framework&lt;/h2&gt;
&lt;p&gt;Drifting from the original scrum implementation, we ended up running our own agility framework. It turned out to have similarity with &lt;a href=&quot;https://en.wikipedia.org/wiki/Kanban_(development)&quot;&gt;kanban&lt;/a&gt; – you don&apos;t always have to invent new things!&lt;/p&gt;
&lt;p&gt;Our main support is a &lt;a href=&quot;http://trello.com&quot;&gt;Trello board&lt;/a&gt; that we share with the whole team. It consists of different columns, where we put card representing small user stories or simple to-do items. Each column is the state of the current card, and we move them left to right:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Ideas&lt;/em&gt;: where we put things we&apos;d like to do or dig into, but there&apos;s no urgency. It might lead to new, smaller ideas, in the &quot;To Do&quot; column.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;To Do&lt;/em&gt;: where we put real things we need to do. We might run a grooming session with our product manager if we need help prioritizing things, but it&apos;s usually not necessary.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Epic&lt;/em&gt;: here we create a few bigger cards that regroup several &lt;em&gt;To Do&lt;/em&gt; items. We don&apos;t move them around, we just archive them when they are fully implemented. There are only 5-6 big cards here at max, which are the long term goals we work on.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Doing&lt;/em&gt;: where we move cards from &lt;em&gt;To Do&lt;/em&gt; when we start doing them. At this stage, we also add people working on the task to the card, so we see a little face of people involved.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Under review&lt;/em&gt;: 90% of our job being done upstream, we usually move cards done and waiting for feedback from the community to this column. When the patches are approved and the card is complete, we move the card to &lt;em&gt;Done&lt;/em&gt;. If a patch needs further improvement, we move back the card to &lt;em&gt;Doing&lt;/em&gt; and work on it, and then move it back to &lt;em&gt;Under review&lt;/em&gt; when resubmitted.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;On hold / blocked&lt;/em&gt;: some of the tasks we work on might be blocked by external factors. We move cards there to keep track of them.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Done during week #XX&lt;/em&gt;: we create a new list every Monday to stack our done cards by week. This is just easier to display and it allows us to see the cards that we complete each week. We archive lists older than a month, from time to time. It gives a great visual feedback and what has been accomplished and merged every week.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/telemetry-trello.jpg&quot; alt=&quot;telemetry-trello&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We started to automate some of our Trello workflow in a tool called &lt;a href=&quot;https://github.com/jd/trelloha&quot;&gt;Trelloha&lt;/a&gt;. For example, it allows us to track upstream patches sent through Gerrit or GitHub and tick the checkbox items in any card when those are merged.&lt;/p&gt;
&lt;p&gt;We actually don&apos;t put many efforts on our Trello board. It&apos;s just a slightly organized chaos, as are upstream projects. We use it as a lightweight system for taking notes, organizing our thought and letting know what we&apos;re doing and why we&apos;re doing it. That&apos;s where Trello is wonderful because using it has a very low friction: creating, updating and moving card is a one click operation.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;One bias of most engineers is to overthink and over-engineer their workflow, trying to rationalize it. Most of the time, they end up automating which means building processes and bureaucracy. It just slows things down and builds frustration upon everyone. Just embrace chaos and spend time on what matters.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Most of the things we do are linked to external Launchpad bugs, Gerrit reviews or GitHub issues. That means the cards in Trello carry very little information, as everything happens outside, in the wild Internet of open source communities.&lt;/p&gt;
&lt;p&gt;This is very important as we need to avoid any kind of retention of knowledge and information from contributors outside the company. This also makes sure that our internal way of running does not leak outside and (badly) influence outside communities.&lt;/p&gt;
&lt;h3&gt;Retrospectives&lt;/h3&gt;
&lt;p&gt;We also run a retrospective every 2 weeks, which might be the only thing we kept from the scrum practice. It&apos;s actually a good opportunity for us to share our feelings, concerns or jokes. We used to do it using the &lt;a href=&quot;http://retrospectivewiki.org/index.php?title=6_Thinking_Hats_Retrospective&quot;&gt;&lt;em&gt;six thinking hats&lt;/em&gt;&lt;/a&gt; method, but it slowly faded away. In the end, we now use a different Trello board with those columns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Good 😄&lt;/li&gt;
&lt;li&gt;Hopes and Wishes 🎁&lt;/li&gt;
&lt;li&gt;Puzzles and Challenges 🌊&lt;/li&gt;
&lt;li&gt;To improve 😡&lt;/li&gt;
&lt;li&gt;Action Items 🤘&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All teammates fill the board with the card they want, and everyone is free to add themselves to any card. We then run through each card and let people who added their name to it talk about it. The column &quot;Action Items&quot; is usually filled as we speak and discover we should do things. We can then move cards created there to our regular board, in the &lt;em&gt;To Do&lt;/em&gt; column.&lt;/p&gt;
&lt;h3&gt;Central communication&lt;/h3&gt;
&lt;p&gt;Sure, people have different roles in a team, but we dislike bottleneck and single point of failure. Therefore, we are using an internal mailing list where we ask people to send their request and messages to. If people send things related to our team job to one of us personally, we just forward or Cc the list when replying so everyone is aware of what one might be talking about with people external to the team.&lt;/p&gt;
&lt;p&gt;This is very important, as it emphasizes that no team member should be considered &lt;em&gt;special&lt;/em&gt;. Nobody owns more information and knowledge than others, and anybody can jump into a conversation if it has valuable knowledge to share.&lt;/p&gt;
&lt;p&gt;The same applies for our internal IRC channel.&lt;/p&gt;
&lt;p&gt;We also make sure that we discuss only company-specific things on this list or on our internal IRC channel. Everything that can be public and is related to upstream is discussed on external communication medium (IRC, upstream mailing list, etc). This is very important to make sure that we are not blocking anybody outside the Red Hat to join us and contribute to the projects or ideas we work on. We also want to make sure that people working in our company are no more special than other contributors.&lt;/p&gt;
&lt;h2&gt;Improvement&lt;/h2&gt;
&lt;p&gt;We&apos;re pretty happy with our set-up right now, and the team runs pretty smoothly since a few months. We&apos;re still trying to improve, and having a general sense of trust among team members make sure we can openly speak about whatever problem we might have.&lt;/p&gt;
&lt;p&gt;Feel free to share your feedback and own experience of running your own teams in the comment section.&lt;/p&gt;
</content:encoded></item><item><title>Gnocchi 3.0 release</title><link>https://julien.danjou.info/blog/gnocchi-3-0-release/</link><guid isPermaLink="true">https://julien.danjou.info/blog/gnocchi-3-0-release/</guid><description>After a few weeks of hard work with the team, here is the new major version of Gnocchi, stamped 3.0.0. It was very challenging, as we wanted to implement a few big changes in it.</description><pubDate>Mon, 03 Oct 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;After a few weeks of hard work with the team, here is the new major version of Gnocchi, stamped &lt;a href=&quot;https://launchpad.net/gnocchi/3.0/3.0.0&quot;&gt;3.0.0&lt;/a&gt;. It was very challenging, as we wanted to implement a few big changes in it.&lt;/p&gt;
&lt;p&gt;Gnocchi is now using &lt;a href=&quot;http://docs.openstack.org/developer/reno/&quot;&gt;reno&lt;/a&gt; to its maximum and you can read &lt;a href=&quot;http://gnocchi.xyz/releasenotes/3.0.html&quot;&gt;the release notes of the 3.0 branch&lt;/a&gt; online. Some notes might be missing as it is our first release with it, but we are making good progress at writing changelogs for most of our user facing and impacting changes.&lt;/p&gt;
&lt;p&gt;Therefore, I&apos;ll only write here about our big major feature that made us bump the major version number.&lt;/p&gt;
&lt;h2&gt;New storage engine&lt;/h2&gt;
&lt;p&gt;And so the most interesting thing that went in the 3.0 release, is the new storage engine that has been built by me and Gordon Chung during those last months. The original approach of writing data in Gnocchi was really naive, so we had an iterative improvement process since version 1.0, and we&apos;re getting close to something very solid.&lt;/p&gt;
&lt;p&gt;This new version leverages several important features which increase performance by a large factor on Ceph (using &lt;code&gt;write(offset)&lt;/code&gt; rather than &lt;code&gt;read()+write()&lt;/code&gt; to append new points), our recommended back-end.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi3_processtime_readwrite_vs_offset.png&quot; alt=&quot;gnocchi3_processtime_readwrite_vs_offset&quot; /&gt;&lt;/p&gt;
&lt;p&gt;To summarize, since most data points are sent sequentially and ordered, we enhanced the data format to profit from that fact and be able to be appended without reading anything. That only works on Ceph though, which provides the needed features.&lt;/p&gt;
&lt;p&gt;We also enabled data compression on all storage drivers by enabling LZ4 compression (&lt;a href=&quot;https://julien.danjou.info/blog/gnocchi-carbonara-timeseries-compression&quot;&gt;see my previous article and research on the subject&lt;/a&gt;), which obviously offers its own set of challenges when using append-only write. The results are tremendous and decrease data usage by a huge factor:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi3_disksize.png&quot; alt=&quot;gnocchi3_disksize&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The rest of the processing pipeline also has been largely improved:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi3_processtime_post.png&quot; alt=&quot;gnocchi3_processtime_post&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi3_processtime_compress_offset.png&quot; alt=&quot;gnocchi3_processtime_compress_offset&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Overall, we&apos;re delighted with the performance improvement we achieved, and we&apos;re looking forward making even better more progress. Gnocchi is now one of the most performing and scalable timeseries databases out there.&lt;/p&gt;
&lt;h2&gt;Upcoming challenges&lt;/h2&gt;
&lt;p&gt;With that big change done, we&apos;re now heading toward a set of more lightweight improvements. Our &lt;a href=&quot;https://bugs.launchpad.net/gnocchi&quot;&gt;bug tracker&lt;/a&gt; is a good place to learn what might be on our mind (check for the &lt;em&gt;wishlist&lt;/em&gt; bugs).&lt;/p&gt;
&lt;p&gt;Improving our API features and offering a better experience for those coming outside of the real of OpenStack are now on my top priority list.&lt;/p&gt;
&lt;p&gt;But let me know if there&apos;s anything you have scratching you, obviously. 😎&lt;/p&gt;
</content:encoded></item><item><title>AsciiDoc book toolchain released</title><link>https://julien.danjou.info/blog/asciidoc-book-toolchain-released/</link><guid isPermaLink="true">https://julien.danjou.info/blog/asciidoc-book-toolchain-released/</guid><description>Writing a book is a big undertaking. You have to think about what you will actually write, the content, its organization, the examples you want to show, illustrations, etc.  When publishing with the h</description><pubDate>Tue, 20 Sep 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Writing a book is a big undertaking. You have to think about what you will actually write, the content, its organization, the examples you want to show, illustrations, etc.&lt;/p&gt;
&lt;p&gt;When publishing with the help of a regular editor, your job stops there at writing – and that&apos;s already a big and hard enough task. Your editor will handle the publishing process, leaving you free of the printing task. Though they might have their own set of requirements, such as making you work with a word processing tool (think LibreOffice Writer or Microsoft Word).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/kindle-thgtp-real.jpg&quot; alt=&quot;kindle-thgtp-real&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When you self-publish like I did with &lt;a href=&quot;https://thehackerguidetopython.com&quot;&gt;The Hacker&apos;s Guide to Python&lt;/a&gt;, none of that happens. You have to deal yourself with getting your work out there, released and available in a viable format for your readership.&lt;/p&gt;
&lt;p&gt;Most of the time, you need to render your book in different formats. You will have to make sure it works correctly on different devices and that the formatting and content disposition is correct.&lt;/p&gt;
&lt;p&gt;I knew exactly what I wanted exactly when writing my book. I wanted to have the book published in at least PDF (for computer reading) and ePub (for e-readers). I also knew, as an Emacs user, that I did not want to spend hours writing a book in LibreOffice. It&apos;s not for me.&lt;/p&gt;
&lt;p&gt;When I wrote about the &lt;a href=&quot;https://julien.danjou.info/blog/2014/making-of-the-hacker-guide-to-python&quot;&gt;making of The Hacker&apos;s Guide to Python&lt;/a&gt;, I briefly mentioned which tools I used to build the book and that I picked &lt;a href=&quot;http://www.methods.co.nz/asciidoc/&quot;&gt;AsciiDoc&lt;/a&gt; as the input format. It makes it easy to write your book inside your favorite text editor, and AsciiDoc has plenty of output format. Customizing these formats to my liking and requirements was another challenge.&lt;/p&gt;
&lt;p&gt;It took me hours and hours of work to have all the nitty-gritty details right. Today I am happy to announce that I can save you a few hours of work if you also want to publish a book.&lt;/p&gt;
&lt;p&gt;I&apos;ve published a new project on &lt;a href=&quot;https://github.com/jd&quot;&gt;my GitHub&lt;/a&gt; called &lt;a href=&quot;https://github.com/jd/asciidoc-book-toolchain&quot;&gt;asciidoc-book-toolchain&lt;/a&gt;. It is the actual toolchain that I use to build &lt;a href=&quot;https://thehackerguidetopython.com&quot;&gt;The Hacker&apos;s Guide to Python&lt;/a&gt;. It should be easy to use and is able to render any book in HTML, PDF, PDF (printable 6&quot;×9&quot; format), ePub and MOBI.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/asciidoc-book-toolchain.dot.png&quot; alt=&quot;asciidoc-book-toolchain.dot&quot; /&gt;&lt;/p&gt;
&lt;p&gt;So feel free to use it, hack it, pull-request it, or whatever. You don&apos;t have any good excuse to not write a book now! 😇 And if you want to self-publish a book and need some help getting started, let me know, I would be glad giving you a few hints!&lt;/p&gt;
</content:encoded></item><item><title>From decimal to timestamp with MySQL</title><link>https://julien.danjou.info/blog/python-sqlalchemy-from-decimal-to-timestamp/</link><guid isPermaLink="true">https://julien.danjou.info/blog/python-sqlalchemy-from-decimal-to-timestamp/</guid><description>When working with timestamps, one question that often arises is the precision of those timestamps. Most software is good enough with a precision up to the second, and that&apos;s easy. But in some cases, l</description><pubDate>Thu, 08 Sep 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When working with timestamps, one question that often arises is the precision of those timestamps. Most software is good enough with a precision up to the second, and that&apos;s easy. But in some cases, like working on metering, a finer precision is required.&lt;/p&gt;
&lt;p&gt;I don&apos;t know exactly why, and it makes me suffer every day, but &lt;a href=&quot;http://openstack.org&quot;&gt;OpenStack&lt;/a&gt; is really tied to &lt;a href=&quot;http://mysql.com&quot;&gt;MySQL&lt;/a&gt; (and its clones). It hurts because MySQL is a very poor solution if you want to leverage your database to actually solve problems. But that&apos;s how life is, unfair. And in the context of the projects I work on, that boils down to that we can&apos;t afford to not support MySQL.&lt;/p&gt;
&lt;p&gt;So here we are, needing to work with MySQL and at the same time requiring timestamp with a finer precision than just seconds. And guess what: MySQL did not support that until 2011.&lt;/p&gt;
&lt;h2&gt;No microseconds in MySQL? No problem: DECIMAL!&lt;/h2&gt;
&lt;p&gt;MySQL 5.6.4 (released in 2011), a beta version of MySQL 5.6 (hello MySQL, ever heard of &lt;a href=&quot;http://semver.org&quot;&gt;Semantic Versioning&lt;/a&gt;?), brought microsecond precision to timestamps. But the first stable version supporting that, MySQL 5.6.10, was only released in 2013. So for a long time, there was a problem without any solution.&lt;/p&gt;
&lt;p&gt;The obvious workaround, in this case, is to reassess your choices in technologies, discover that &lt;a href=&quot;https://www.postgresql.org/docs/7.1/static/datatype-datetime.html&quot;&gt;PostgreSQL supports microsecond precision for at least a decade&lt;/a&gt; and problem solved.&lt;/p&gt;
&lt;p&gt;This is not what happened in our case, and in order to support MySQL, one had to find a workaround. And so did they in our &lt;a href=&quot;http://launchpad.net/ceilometer&quot;&gt;Ceilometer&lt;/a&gt; project, using a &lt;a href=&quot;https://dev.mysql.com/doc/refman/5.7/en/precision-math-decimal-characteristics.html&quot;&gt;&lt;code&gt;DECIMAL&lt;/code&gt;&lt;/a&gt; type instead of &lt;code&gt;DATETIME&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;DECIMAL&lt;/code&gt; type takes 2 arguments: the total number of digits you need to store, and how many in that total will be used for the fractional part. Knowing that the internal storage of MySQL uses 1 byte for 2 digits, 2 bytes for 4 digits, 3 bytes for 6 digits and 4 bytes for 9 digits, and that each part is stored independently, in order to maximize your storage space, you want to pick a number of digits that fits that correctly.&lt;/p&gt;
&lt;p&gt;This is why Ceilometer picked 14 for the integer part (9 digits on 4 bytes and 5 digits on 3 bytes) and 6 for the decimal part (3 bytes).&lt;/p&gt;
&lt;p&gt;Wait. It&apos;s stupid because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;DECIMAL(20, 6)&lt;/code&gt; implies that you uses 14 digits for the integer part, which using epoch as a reference makes you able to encode timestamp &lt;code&gt;(10^14) - 1&lt;/code&gt; which is year 3170843. I am certain Ceilometer won&apos;t last that far.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;14 digits is 9 + 5 digits in MySQL which is 7 bytes, the same size that is used for 9 + 6 digits. So if you could have &lt;code&gt;DECIMAL(21, 6)&lt;/code&gt; for the same storage space (and go up to year 31690708 which is a nice bonus, right?)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Well, I guess the original author of the patch did not read the documentation entirely (&lt;code&gt;DECIMAL(20, 6)&lt;/code&gt; being on the MySQL documentation page as an example, I imagine it just has been copy-pasted blindly?).&lt;/p&gt;
&lt;p&gt;The best choice for this use case would have been &lt;code&gt;DECIMAL(17, 6)&lt;/code&gt; which would allow storing 11 digits for integer (5 bytes), supporting timestamp up to &lt;code&gt;(2^11)-1&lt;/code&gt; (year 5138), and 6 digits for decimal part (3 bytes), using only 8 bytes in total per timestamp.&lt;/p&gt;
&lt;p&gt;Nonetheless, this workaround has been implemented using a &lt;a href=&quot;http://sqlalchemy.org&quot;&gt;SQLAlchemy&lt;/a&gt; custom type and works as expected:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class PreciseTimestamp(sqlalchemy.types.TypeDecorator):
    &quot;&quot;&quot;Represents a timestamp precise to the microsecond.&quot;&quot;&quot;

    impl = sqlalchemy.DateTime

    def load_dialect_impl(self, dialect):
        if dialect.name == &apos;mysql&apos;:
            return sqlalchemy.dialect.type_descriptor(
                sqlalchemy.types.DECIMAL(precision=20,
                                         scale=6,
                                         asdecimal=True))
        return sqlalchemy.dialect.type_descriptor(self.impl)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Microseconds in MySQL? Damn, migration!&lt;/h2&gt;
&lt;p&gt;As I said, MySQL 5.6.4 brought microseconds precision to the table (pun intended). Therefore, it&apos;s a great time to migrate away from this hackish format to the brand new one.&lt;/p&gt;
&lt;p&gt;First, be aware that the default &lt;code&gt;DATETIME&lt;/code&gt; type has no microseconds precision: &lt;a href=&quot;http://dev.mysql.com/doc/refman/5.7/en/datetime.html&quot;&gt;you have to specify how many digits you want as an argument&lt;/a&gt;.&lt;br /&gt;
To support microseconds, you should therefore use &lt;code&gt;DATETIME(6)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If we were using a great RDBMS, let&apos;s say, hum, PostgreSQL, we could do that&lt;br /&gt;
very easily, see:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;postgres=# CREATE TABLE foo (mytime decimal);
CREATE TABLE
postgres=# \d foo
      Table &quot;public.foo&quot;
 Column │  Type   │ Modifiers
────────┼─────────┼───────────
 mytime │ numeric │
postgres=# INSERT INTO foo (mytime) VALUES (1473254401.234);
INSERT 0 1
postgres=# ALTER TABLE foo ALTER COLUMN mytime SET DATA TYPE timestamp with time zone USING to_timestamp(mytime);
ALTER TABLE
postgres=# \d foo
              Table &quot;public.foo&quot;
 Column │           Type           │ Modifiers
────────┼──────────────────────────┼───────────
 mytime │ timestamp with time zone │

postgres=# select * from foo;
           mytime
────────────────────────────
 2016-09-07 13:20:01.234+00
(1 row)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And since this is a pretty common use case, it&apos;s even &lt;a href=&quot;https://www.postgresql.org/docs/9.5/static/sql-altertable.html&quot;&gt;an example in the PostgreSQL documentation&lt;/a&gt;. The version from the documentation uses a calculation based on epoch, whereas my example here leverages the &lt;code&gt;to_timestamp()&lt;/code&gt; function. That&apos;s my personal touch.&lt;/p&gt;
&lt;p&gt;Obviously, doing this conversion in a single line is not possible with MySQL: it does not implement the &lt;code&gt;USING&lt;/code&gt; keyword on &lt;code&gt;ALTER TABLE … ALTER COLUMN&lt;/code&gt;. So what&apos;s the solution gonna be? Well, it&apos;s a 4 steps job:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a new column of type &lt;code&gt;DATETIME(6)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Copy data from the old column to the new column, converting them to the new format&lt;/li&gt;
&lt;li&gt;Delete the old column&lt;/li&gt;
&lt;li&gt;Rename the new column to the old column name.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;But I know what you&apos;re thinking: there are 4 steps, but that&apos;s not a problem, we&apos;ll just use a transaction and embed these operations inside.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://dev.mysql.com/doc/refman/5.7/en/cannot-roll-back.html&quot;&gt;MySQL does not support transactions on data definition language (DDL)&lt;/a&gt;.&lt;br /&gt;
So if any of those steps fails, you&apos;ll be unable rollback steps 1, 3 and 4. Who knew that using MySQL was like living on the edge, right?&lt;/p&gt;
&lt;h2&gt;Doing this in Python with our friend Alembic&lt;/h2&gt;
&lt;p&gt;I like &lt;a href=&quot;http://alembic.zzzcomputing.com/&quot;&gt;Alembic&lt;/a&gt;. It&apos;s a Python library based on &lt;a href=&quot;http://sqlalchemy.org&quot;&gt;SQLAlchemy&lt;/a&gt; that handles schema migration for your favorite RDBMS.&lt;/p&gt;
&lt;p&gt;Once you created a new alembic migration script using &lt;code&gt;alembic revision&lt;/code&gt;, it&apos;s time to edit it and write something along those lines:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from alembic import op
import sqlalchemy as sa
from sqlalchemy.sql import func

class Timestamp(sa.types.TypeDecorator):
    &quot;&quot;&quot;Represents a timestamp precise to the microsecond.&quot;&quot;&quot;

    impl = sqlalchemy.DateTime

    def load_dialect_impl(self, dialect):
        if dialect.name == &apos;mysql&apos;:
            return dialect.type_descriptor(mysql.DATETIME(fsp=6))
        return self.impl

def upgrade():
    bind = op.get_bind()
    if bind and bind.engine.name == &quot;mysql&quot;:
        existing_type = sa.types.DECIMAL(
            precision=20, scale=6, asdecimal=True)
        existing_col = sa.Column(&quot;mytime&quot;, existing_type, nullable=False)
        temp_col = sa.Column(&quot;mytime_ts&quot;, Timestamp(), nullable=False)
        # Step 1: ALTER TABLE mytable ADD COLUMN mytime_ts DATETIME(6)
        op.add_column(&quot;mytable&quot;, temp_col)
        t = sa.sql.table(&quot;mytable&quot;, existing_col, temp_col)
        # Step 2: UPDATE mytable SET mytime_ts=from_unixtime(mytime)
        op.execute(t.update().values(mytime_ts=func.from_unixtime(existing_col)}))
        # Step 3: ALTER TABLE mytable DROP COLUMN mytime
        op.drop_column(&quot;mytable&quot;, &quot;mytime&quot;)
        # Step 4: ALTER TABLE mytable CHANGE mytime_ts mytime
        # Note: MySQL needs to have all the old/new information to just rename a column…
        op.alter_column(&quot;mytable&quot;,
                        &quot;mytime_ts&quot;,
                        nullable=False,
                        type_=Timestamp(),
                        existing_nullable=False,
                        existing_type=existing_type,
                        new_column_name=&quot;mytime&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In MySQL, the function to convert a float to a UNIX timestamp is &lt;code&gt;from_unixtime()&lt;/code&gt;, so the script leverages it to convert the data. As said, you&apos;ll notice we don&apos;t bother using any kind of transaction, so if anything goes wrong, there&apos;s no rollback, and it won&apos;t be possible to re-run the migration without a manual intervention.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TimestampUTC&lt;/code&gt; is a custom class that implements &lt;code&gt;sqlalchemy.DateTime&lt;/code&gt; using a &lt;code&gt;DATETIME(6)&lt;/code&gt; type for MySQL, and a regular &lt;code&gt;sqlalchemy.DateTime&lt;/code&gt; type for other back-ends. It is used by the rest of the code (e.g. ORM model) but I&apos;ve pasted it in this example for a better understanding.&lt;/p&gt;
&lt;p&gt;Once written, you can easily test your migration using &lt;a href=&quot;https://github.com/jd/pifpaf&quot;&gt;&lt;em&gt;pifpaf&lt;/em&gt;&lt;/a&gt; to run a temporary database:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ pifpaf run mysql $SHELL
$ alembic -c alembic/alembic.ini upgrade 1c98ac614015 # upgrade to the initial revision
$ mysql -S $PIFPAF_MYSQL_SOCKET pifpaf
mysql&amp;gt; INSERT INTO mytable (mytime) VALUES (1325419200.213000);
Query OK, 1 row affected (0.00 sec)

mysql&amp;gt; SELECT * FROM mytable;
+-------------------+
| mytime            |
+-------------------+
| 1325419200.213000 |
+-------------------+
1 row in set (0.00 sec)

$ alembic -c alembic/alembic.ini upgrade head

$ mysql -S $PIFPAF_MYSQL_SOCKET pifpaf
mysql&amp;gt; SELECT * FROM mytable;
+----------------------------+
| mytime                     |
+----------------------------+
| 2012-01-01 13:00:00.213000 |
+----------------------------+
1 row in set (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And voilà, we just migrated unsafely our data to a new fancy format. Thank you Alembic for solving a problem we would not have without MySQL. 😊&lt;/p&gt;
</content:encoded></item><item><title>A retrospective of the OpenStack Telemetry project Newton cycle</title><link>https://julien.danjou.info/blog/retrospective-openstack-telemetry-newton/</link><guid isPermaLink="true">https://julien.danjou.info/blog/retrospective-openstack-telemetry-newton/</guid><description>A few weeks ago, I recorded an interview with Krishnan Raghuram about what was discussed for this development cycle for OpenStack Telemetry at the Austin summit.  It&apos;s interesting to look back at this</description><pubDate>Mon, 05 Sep 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few weeks ago, I recorded an interview with Krishnan Raghuram about what was discussed for this development cycle for OpenStack Telemetry at the Austin summit.&lt;/p&gt;
&lt;p&gt;It&apos;s interesting to look back at this video more than 3 months after recording it, and see what actually happened to Telemetry. It turns out that some of the things that I think were going to happen did not happen yet. As the first release candidate version is approaching, it&apos;s very unlikely they happen.&lt;/p&gt;
&lt;p&gt;And on the other side, some new fancy features arrived suddenly without me having a clue about them.&lt;/p&gt;
&lt;p&gt;As far as &lt;strong&gt;Ceilometer&lt;/strong&gt; is concerned, here&apos;s the list of what really happened in terms of user features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Added full support for SNMP v3 USM model&lt;/li&gt;
&lt;li&gt;Added support for batch measurement in Gnocchi dispatcher&lt;/li&gt;
&lt;li&gt;Set ended_at timestamp in Gnocchi dispatcher&lt;/li&gt;
&lt;li&gt;Allow Swift pollster to specify regions&lt;/li&gt;
&lt;li&gt;Add L3 cache usage and memory bandwidth meters&lt;/li&gt;
&lt;li&gt;Split out the event code (REST API and storage) to a new &lt;strong&gt;Panko&lt;/strong&gt; project&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And a few other minor things. I planned none of them except Panko (which I was responsible for), and the ones we planned (documentation update, pipeline rework and polling enhancement) did not happen yet.&lt;/p&gt;
&lt;p&gt;For &lt;strong&gt;Aodh&lt;/strong&gt;, we expected to rework the documentation entirely too, and that did not happen either. What we did instead:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deprecate and disable combination alarms&lt;/li&gt;
&lt;li&gt;Add pagination support in REST API&lt;/li&gt;
&lt;li&gt;Deprecated all non-SQL database store and provide a tool to migrate&lt;/li&gt;
&lt;li&gt;Support batch notification for aodh-notifier&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&apos;s definitely a good list of new features for Aodh, still small, but simplifying it, removing technical debt and continuing building momentum around it.&lt;/p&gt;
&lt;p&gt;For &lt;strong&gt;Gnocchi&lt;/strong&gt;, we really had no plan, except maybe a few small features (they&apos;re usually tracked in the Launchpad bug list). It turned out we had some fancy new idea with Gordon Chung on how to boost our storage engine, so we work on that. That kept us busy a few weeks in the end, though the preliminary results look tremendous – so it was definitely worth it. We also have a AWS S3 storage driver on its way.&lt;/p&gt;
&lt;p&gt;I find this exercise interesting, as it really emphasizes how you can&apos;t really control what&apos;s happening in any open source project, where your contributors come and go and work on their own agenda.&lt;/p&gt;
&lt;p&gt;That does not mean we&apos;re dropping the themes and ideas I&apos;ve laid out in that video. We&apos;re still pushing our &quot;documentation is mandatory&quot; policy and improving our &quot;work by default&quot; scenario. It&apos;s just a longer road that we expected.&lt;/p&gt;
</content:encoded></item><item><title>The definitive guide to Python exceptions</title><link>https://julien.danjou.info/blog/python-exceptions-guide/</link><guid isPermaLink="true">https://julien.danjou.info/blog/python-exceptions-guide/</guid><description>Three years after my definitive guide on Python classic, static, class and abstract methods, it seems to be time for a new one. Here, I would like to dissect and discuss Python exceptions.</description><pubDate>Thu, 11 Aug 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Three years after my definitive guide on &lt;a href=&quot;https://julien.danjou.info/blog/guide-python-static-class-abstract-methods&quot;&gt;Python classic, static, class and abstract methods&lt;/a&gt;, it seems to be time for a new one. Here, I would like to dissect and discuss &lt;a href=&quot;https://docs.python.org/3/tutorial/errors.html&quot;&gt;Python exceptions&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Dissecting the base exceptions&lt;/h2&gt;
&lt;p&gt;In Python, the base exception class is named &lt;code&gt;BaseException&lt;/code&gt;. Being rarely used in any program or library, it ought to be considered as an &lt;em&gt;implementation detail&lt;/em&gt;. But to discover how it&apos;s implemented, you can go and read &lt;a href=&quot;https://github.com/python/cpython/blob/master/Objects/exceptions.c&quot;&gt;Objects/exceptions.c&lt;/a&gt; in the CPython source code. In that file, what is interesting is to see that the &lt;code&gt;BaseException&lt;/code&gt; class defines all the basic methods and attribute of exceptions. The basic well-known &lt;code&gt;Exception&lt;/code&gt; class is then simply defined as a subclass of &lt;code&gt;BaseException&lt;/code&gt;, nothing more:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/*
 *    Exception extends BaseException
 */
SimpleExtendsException(PyExc_BaseException, Exception,
                       &quot;Common base class for all non-exit exceptions.&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The only other exceptions that inherits directly from &lt;code&gt;BaseException&lt;/code&gt; are &lt;code&gt;GeneratorExit&lt;/code&gt;, &lt;code&gt;SystemExit&lt;/code&gt; and &lt;code&gt;KeyboardInterrupt&lt;/code&gt;. All the other builtin exceptions inherits from &lt;code&gt;Exception&lt;/code&gt;. The whole hierarchy can be seen by running &lt;code&gt;pydoc2 exceptions&lt;/code&gt; or &lt;code&gt;pydoc3 builtins&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here are the graph representing the builtin exceptions inheritance in Python 2 and Python 3 (generated using &lt;a href=&quot;https://github.com/jd/julien.danjou.info/blob/master/bin/generate-python-exceptions-graph.py&quot;&gt;this script&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/python2-exceptions-graph.png&quot; alt=&quot;Python 2 builtin exceptions inheritance graph&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/python3-exceptions-graph.png&quot; alt=&quot;Python 3 builtin exceptions inheritance graph&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;BaseException.__init__&lt;/code&gt; signature is actually &lt;code&gt;BaseException.__init__(*args)&lt;/code&gt;. This initialization method stores any arguments that is passed in the &lt;code&gt;args&lt;/code&gt; attribute of the exception. This can be seen in the &lt;code&gt;exceptions.c&lt;/code&gt; source code – and is true for both Python 2 and Python 3:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static int
BaseException_init(PyBaseExceptionObject *self, PyObject *args, PyObject *kwds)
{
    if (!_PyArg_NoKeywords(Py_TYPE(self)-&amp;gt;tp_name, kwds))
        return -1;

    Py_INCREF(args);
    Py_XSETREF(self-&amp;gt;args, args);

    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The only place where this &lt;code&gt;args&lt;/code&gt; attribute is used is in the &lt;code&gt;BaseException.__str__&lt;/code&gt; method. This method uses &lt;code&gt;self.args&lt;/code&gt; to convert an exception to a string:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static PyObject *
BaseException_str(PyBaseExceptionObject *self)
{
    switch (PyTuple_GET_SIZE(self-&amp;gt;args)) {
    case 0:
        return PyUnicode_FromString(&quot;&quot;);
    case 1:
        return PyObject_Str(PyTuple_GET_ITEM(self-&amp;gt;args, 0));
    default:
        return PyObject_Str(self-&amp;gt;args);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This can be translated in Python to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def __str__(self):
    if len(self.args) == 0:
        return &quot;&quot;
    if len(self.args) == 1:
        return str(self.args[0])
    return str(self.args)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Therefore, the message to display for an exception should be passed as the first and the only argument to the &lt;code&gt;BaseException.__init__&lt;/code&gt; method.&lt;/p&gt;
&lt;h2&gt;Defining your exceptions properly&lt;/h2&gt;
&lt;p&gt;As you may already know, in Python, exceptions can be raised in any part of the program. The basic exception is called &lt;code&gt;Exception&lt;/code&gt; and can be used anywhere in your program. In real life, however no program nor library should ever raise &lt;code&gt;Exception&lt;/code&gt; directly: it&apos;s not specific enough to be helpful.&lt;/p&gt;
&lt;p&gt;Since all exceptions are expected to be derived from the base class &lt;code&gt;Exception&lt;/code&gt;, this base class can easily be used as a catch-all:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;try:
    do_something()
except Exception:
    # THis will catch any exception!
    print(&quot;Something terrible happened&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To define your own exceptions correctly, there are a few rules and best practice that you need to follow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Always inherit from (at least) &lt;code&gt;Exception&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;class MyOwnError(Exception):
    pass
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Leverage what we saw earlier about &lt;code&gt;BaseException.__str__&lt;/code&gt;: it uses the first argument passed to &lt;code&gt;BaseException.__init__&lt;/code&gt; to be printed, so always call &lt;code&gt;BaseException.__init__&lt;/code&gt; with &lt;strong&gt;only one argument&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When building a library, define a base class inheriting from &lt;code&gt;Excepion&lt;/code&gt;. It will make it easier for consumers to catch any exception from the library:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;class ShoeError(Exception):
    &quot;&quot;&quot;Basic exception for errors raised by shoes&quot;&quot;&quot;

class UntiedShoelace(ShoeError):
    &quot;&quot;&quot;You could fall&quot;&quot;&quot;

class WrongFoot(ShoeError):
    &quot;&quot;&quot;When you try to wear your left show on your right foot&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It then makes it easy to use &lt;code&gt;except ShoeError&lt;/code&gt; when doing anything with that piece of code related to shoes. For example, &lt;a href=&quot;https://docs.djangoproject.com/en/1.9/_modules/django/core/exceptions/&quot;&gt;Django does not do that&lt;/a&gt; for some of its exceptions, making it hard to catch &quot;any exception raised by Django&quot;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Provide details about the error. This is extremely valuable to be able to log correctly errors or take further action and try to recover:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;class CarError(Exception):
    &quot;&quot;&quot;Basic exception for errors raised by cars&quot;&quot;&quot;
    def __init__(self, car, msg=None):
        if msg is None:
            # Set some default useful error message
            msg = &quot;An error occured with car %s&quot; % car
        super(CarError, self).__init__(msg)
        self.car = car

class CarCrashError(CarError):
    &quot;&quot;&quot;When you drive too fast&quot;&quot;&quot;
    def __init__(self, car, other_car, speed):
        super(CarCrashError, self).__init__(
            car, msg=&quot;Car crashed into %s at speed %d&quot; % (other_car, speed))
        self.speed = speed
        self.other_car = other_car
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, any code can inspect the exception to take further action:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;try:
    drive_car(car)
except CarCrashError as e:
    # If we crash at high speed, we call emergency
    if e.speed &amp;gt;= 30:
        call_911()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example, this is leveraged in &lt;a href=&quot;http://gnocchi.xyz&quot;&gt;Gnocchi&lt;/a&gt; to raise specific application exceptions (&lt;code&gt;NoSuchArchivePolicy&lt;/code&gt;) on expected foreign key violations raised by SQL constraints:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;try:
    with self.facade.writer() as session:
        session.add(m)
except exception.DBReferenceError as e:
    if e.constraint == &apos;fk_metric_ap_name_ap_name&apos;:
        raise indexer.NoSuchArchivePolicy(archive_policy_name)
    raise
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Inherits from builtin exceptions types when it makes sense. This makes it easier for programs to not be specific to your application or library:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;class CarError(Exception):
    &quot;&quot;&quot;Basic exception for errors raised by cars&quot;&quot;&quot;

class InvalidColor(CarError, ValueError):
    &quot;&quot;&quot;Raised when the color for a car is invalid&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That allows many programs to catch errors in a more generic way without noticing your own defined type. If a program already knows how to handle a &lt;code&gt;ValueError&lt;/code&gt;, it won&apos;t need any specific code nor modification.&lt;/p&gt;
&lt;h2&gt;Organization&lt;/h2&gt;
&lt;p&gt;Organizing code can be quite touchy and complicated. I cover more general rules in &lt;a href=&quot;https://thehackerguidetopython.com&quot;&gt;The Hacker&apos;s Guide to Python&lt;/a&gt;, but here&apos;s a few rules concerning exceptions in particular.&lt;/p&gt;
&lt;p&gt;There is no limitation on where and when you can define exceptions. As they are, after all, normal classes, they can be defined in any module, function or class – even as closures.&lt;/p&gt;
&lt;p&gt;Most libraries package their exceptions into a specific exception module: &lt;a href=&quot;http://sqlalchemy.org&quot;&gt;SQLAlchemy&lt;/a&gt; has them in&lt;br /&gt;
&lt;a href=&quot;http://docs.sqlalchemy.org/en/latest/core/exceptions.html&quot;&gt;&lt;code&gt;sqlalchemy.exc&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;http://docs.python-requests.org/&quot;&gt;requests&lt;/a&gt; has them in&lt;br /&gt;
&lt;a href=&quot;http://docs.python-requests.org/en/master/_modules/requests/exceptions/&quot;&gt;&lt;code&gt;requests.exceptions&lt;/code&gt;&lt;/a&gt;, &lt;a href=&quot;http://werkzeug.pocoo.org/&quot;&gt;Werkzeug&lt;/a&gt; has them in &lt;a href=&quot;http://werkzeug.pocoo.org/docs/0.11/exceptions/&quot;&gt;&lt;code&gt;werkzeug.exceptions&lt;/code&gt;&lt;/a&gt;, etc.&lt;/p&gt;
&lt;p&gt;That makes sense for libraries to export exceptions that way, as it makes it very easy for consumers to import their exception module and know where the exceptions are defined when writing code to handle errors.&lt;/p&gt;
&lt;p&gt;This is not mandatory, and smaller Python modules might want to retain their exceptions into their sole module. Typically, if your module is small enough to be kept in one file, don&apos;t bother splitting your exceptions into a different file/module.&lt;/p&gt;
&lt;p&gt;While this wisely applies to libraries, applications tend to be different beasts. Usually, they are composed of different subsystems, where each one might have its own set of exceptions. This is why I generally discourage going with only one exception module in an application, but to split them across the different parts of one&apos;s program. There might be no need of a special &lt;code&gt;myapp.exceptions&lt;/code&gt; module.&lt;/p&gt;
&lt;p&gt;For example, if your application is composed of an HTTP REST API defined into the module &lt;code&gt;myapp.http&lt;/code&gt; and of a TCP server contained into &lt;code&gt;myapp.tcp&lt;/code&gt;, it&apos;s likely they can both define different exceptions tied to their own protocol errors and cycle of life. Defining those exceptions in a &lt;code&gt;myapp.exceptions&lt;/code&gt; module would just scatter the code for the sake of some useless consistency. If the exceptions are local to a file, just define them somewhere at the top of that file. It will simplify the maintenance of the code.&lt;/p&gt;
&lt;h2&gt;Wrapping exceptions&lt;/h2&gt;
&lt;p&gt;Wrapping exception is the practice by which one exception is encapsulated into another:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MylibError(Exception):
    &quot;&quot;&quot;Generic exception for mylib&quot;&quot;&quot;
    def __init__(self, msg, original_exception):
        super(MylibError, self).__init__(msg + (&quot;: %s&quot; % original_exception))
        self.original_exception = original_exception

try:
    requests.get(&quot;http://example.com&quot;)
except requests.exceptions.ConnectionError as e:
     raise MylibError(&quot;Unable to connect&quot;, e)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This makes sense when writing a library which leverages other libraries. If a library uses &lt;code&gt;requests&lt;/code&gt; and does not encapsulate &lt;code&gt;requests&lt;/code&gt; exceptions into its own defined error classes, it will be a case of layer violation. Any application using your library might receive a &lt;code&gt;requests.exceptions.ConnectionError&lt;/code&gt;, which is a problem because:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The application has no clue that the library was using &lt;code&gt;requests&lt;/code&gt; and does not need/want to know about it.&lt;/li&gt;
&lt;li&gt;The application will have to import &lt;code&gt;requests.exceptions&lt;/code&gt; itself and therefore will depend on &lt;code&gt;requests&lt;/code&gt; – even if it does not use it directly.&lt;/li&gt;
&lt;li&gt;As soon as &lt;code&gt;mylib&lt;/code&gt; changes from &lt;code&gt;requests&lt;/code&gt; to e.g. &lt;code&gt;httplib2&lt;/code&gt;, the application code catching &lt;code&gt;requests&lt;/code&gt; exceptions will become irrelevant.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/openstack/tooz&quot;&gt;Tooz&lt;/a&gt; library is a good example of wrapping, as it uses a driver-based approach and depends on a lot of different Python modules to talk to different backends (ZooKeeper, PostgreSQL, etcd…).&lt;br /&gt;
Therefore, it wraps exception from other modules on every occasion into its own set of error classes. Python 3 introduced the &lt;code&gt;raise from&lt;/code&gt; form to help with that, and that&apos;s what Tooz leverages to raise its own error.&lt;/p&gt;
&lt;p&gt;It&apos;s also possible to encapsulate the original exception into a custom defined exception, as done above. That makes the original exception available for inspection easily.&lt;/p&gt;
&lt;h2&gt;Catching and logging&lt;/h2&gt;
&lt;p&gt;When designing exceptions, it&apos;s important to remember that they should be targeted both at humans and computers. That&apos;s why they should include an explicit message, and embed as much information as possible. That will help to debug and write resilient programs that can pivot their behavior depending on the attributes of exception, as seen above.&lt;/p&gt;
&lt;p&gt;Also, silencing exceptions completely is to be considered as bad practice. You should not write code like that:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;try:
    do_something()
except Exception:
    # Whatever
    pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Not having any kind of information in a program where an exception occurs is a nightmare to debug.&lt;/p&gt;
&lt;p&gt;If you use (and you should) the &lt;a href=&quot;https://docs.python.org/3/library/logging.html&quot;&gt;&lt;code&gt;logging&lt;/code&gt;&lt;/a&gt; library, you can use the &lt;code&gt;exc_info&lt;/code&gt; parameter to log a complete traceback when an exception occurs, which might help debugging on severe and unrecoverable failure:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;try:
    do_something()
except Exception:
    logging.getLogger().error(&quot;Something bad happened&quot;, exc_info=True)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you often forget on how to setup the &lt;code&gt;logging&lt;/code&gt; library, you should check out &lt;a href=&quot;https://github.com/jd/daiquiri&quot;&gt;daiquiri&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Further reading&lt;/h2&gt;
&lt;p&gt;If you understood everything so far, congratulations, you might be ready to handle exception in Python! If you want to have a broader scope on exceptions and what Python misses, I encourage you to read about &lt;a href=&quot;https://en.wikipedia.org/wiki/Exception_handling#Condition_systems&quot;&gt;condition systems&lt;/a&gt; and discover the generalization of exceptions – that I hope we&apos;ll see in Python one day!&lt;/p&gt;
&lt;p&gt;I hope this will help you building better libraries and application. Feel free to shoot any question in the comment section!&lt;/p&gt;
</content:encoded></item><item><title>The bad practice in FOSS projects management</title><link>https://julien.danjou.info/blog/foss-projects-management-bad-practice/</link><guid isPermaLink="true">https://julien.danjou.info/blog/foss-projects-management-bad-practice/</guid><description>During the OpenStack summit a few weeks ago, I had the chance to talk to some people about my experience on running open source projects. It turns out that after hanging out in communities and contrib</description><pubDate>Thu, 09 Jun 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;During the OpenStack summit a few weeks ago, I had the chance to talk to some people about my experience on running open source projects. It turns out that after hanging out in communities and contributing to many projects for years, I may be able to provide some hindsight and an external eye to many of those who are new to it.&lt;/p&gt;
&lt;p&gt;There are plenty of resource explaining how to run an open source projects out there. Today, I would like to take a different angle and emphasize what you should not &lt;em&gt;socially&lt;/em&gt; do in your projects. This list comes from various open source projects I encountered these past years. I&apos;m going to go through some of the bad practice I&apos;ve spotted, in a random order, illustrated by some concrete example.&lt;/p&gt;
&lt;h2&gt;Seeing contributors as an annoyance&lt;/h2&gt;
&lt;p&gt;When software developers and maintainers are busy, there&apos;s one thing they don&apos;t need: more work. To many people, the instinctive reactions to external contribution is: damn, more work. And actually, it is.&lt;/p&gt;
&lt;p&gt;Therefore, some maintainers tend to avoid that surplus of work: they state they don&apos;t want contributions, or make contributors feel un-welcomed. This can take a lot of different forms, from ignoring them to being unpleasant. It indeed avoids the immediate need to deal with the work that has been added on the maintainer shoulders.&lt;/p&gt;
&lt;p&gt;This is one of the biggest mistake and misconception of open source. If people are sending you more work, you should do whatever it takes to feel them welcome so they continue working with you. They might pretty soon become the guys doing the work you are doing instead of you. Think: retirement!&lt;/p&gt;
&lt;p&gt;Let&apos;s take a look at my friend Gordon, who I saw starting as a Ceilometer contributor in 2013. He was doing great code reviews, but he was actually giving me more work by catching bugs in my patches and sending patches I had to review. Instead of being a bully so he would stop making me rework my code and reviews his patches, &lt;a href=&quot;http://lists.openstack.org/pipermail/openstack-dev/2013-May/008975.html&quot;&gt;I requested that we trust him even more by adding him as a core reviewer&lt;/a&gt;. time contribution.&lt;/p&gt;
&lt;p&gt;And if they don&apos;t do this one-time contribution, they won&apos;t make it two. They won&apos;t make any. Those projects may have just lost their new maintainers.&lt;/p&gt;
&lt;h2&gt;Letting people only do the grunt work&lt;/h2&gt;
&lt;p&gt;When new contributors arrive and want to contribute to a particular project, they may have very different motivation. Some of them are users, but some of them are just people looking to see how it is to contribute. Getting the thrill of contribution, as an exercise, or as a willingness to learn and start contributing back to the ecosystem they use.&lt;/p&gt;
&lt;p&gt;The usual response from maintainers is to push people into doing grunt work. That means doing jobs that have no interest, little value, and probably no direct impact on the project.&lt;/p&gt;
&lt;p&gt;Some people actually have no problem with it, some have. Some will feel offended to do low impact work, and some will love it as soon as you give them some sort of acknowledgment. Be aware of it, and be sure to high-five people doing it. That&apos;s the only way to keep them around.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/computer-coding.jpg&quot; alt=&quot;computer-coding&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Not valorizing small contributions&lt;/h2&gt;
&lt;p&gt;When the first patch that comes in from a new contributor is a typo fix, what developers think? That they don&apos;t care, that you&apos;re wasting their precious time with your small contribution. And nobody cares about bad English in the documentation, don&apos;t they?&lt;/p&gt;
&lt;p&gt;This is wrong. See my first contributions to &lt;a href=&quot;https://github.com/home-assistant/home-assistant/commit/36cb12cd157b22bdc1fa28b700ca0fb751cca7a4&quot;&gt;home-assistant&lt;/a&gt; and &lt;a href=&quot;https://github.com/marijnh/Postmodern/commit/ec537f72393e1032853b78e0b7b4d0ff98632a02&quot;&gt;Postmodern&lt;/a&gt;: I fixed typos in the documentation.&lt;/p&gt;
&lt;p&gt;I contributed to &lt;a href=&quot;http://orgmode.org&quot;&gt;Org-mode&lt;/a&gt; for a few years. &lt;a href=&quot;http://repo.or.cz/org-mode.git/commit/a153f5a31dffbc6b78a8c5d8d027961abe585a38&quot;&gt;My first patch to orgmode&lt;/a&gt; was about fixing a docstring. Then, I sent 56 patches, fixing bugs and adding fancy features and also wrote a few external modules. To this day, I&apos;m still #16 in the top-committer list of Org-mode who contains 390 contributors. So not that would call a small contributor. I am sure the community is glad they did not despise my documentation fix.&lt;/p&gt;
&lt;h2&gt;Setting the bar too high for new comers&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/too-high.png&quot; alt=&quot;too-high&quot; /&gt;&lt;/p&gt;
&lt;p&gt;When new contributors arrive, their knowledge about the project, its context, and the technologies can vary largely. One of the mistakes people often make is to ask contributors too complicated things that they cannot realize. That scares them away (many people are going to be shy or introvert) and they may just disappear, feeling too stupid to help.&lt;/p&gt;
&lt;p&gt;Before making any comment, you should not have any assumption about their knowledge. That should avoid such situation. You also should be very delicate when assessing their skills, as some people might feel vexed if you underestimate them too much.&lt;/p&gt;
&lt;p&gt;Once that level has been properly evaluated (a few exchanges should be enough), you need to mentor to the right degree your contributor so it can blossom. It takes time and experience to master this, and you may likely lose some of them in the process, but it&apos;s a path every maintainer has to take.&lt;/p&gt;
&lt;p&gt;Mentoring is a very important aspect of welcoming new contributors to your project, whatever it is. Pretty sure that applies nicely outside free software too.&lt;/p&gt;
&lt;h2&gt;Requiring people to make sacrifices with their lives&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/balance-stones.jpg&quot; alt=&quot;balance-stones&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is an aspect that varies a lot depending on the project and context, but it&apos;s really important. As a free software project, where most people will contribute on their own good will and sometimes spare time, you must not require them to make big sacrifices. This won&apos;t work.&lt;/p&gt;
&lt;p&gt;One of the worst implementation of that is requiring people to fly 5 000 kilometers to meet in some place to discuss the project. This puts contributors in an unfair position, based on their ability to leave their family for a week, take a plane/boat/car/train, rent an hotel, etc. This is not good, and everything should be avoided to &lt;em&gt;require&lt;/em&gt; people to do that in order to participate and feel included in the project and blend in your community. Don&apos;t get me wrong: that does not me social activities should be prohibited, on the contrary. Just avoid excluding people when you discuss any project.&lt;/p&gt;
&lt;p&gt;The same apply to any other form of discussion that makes it complicated for everyone to participate: IRC meetings (it&apos;s hard for some people to book an hour, especially depending on the timezone they live in), video-conference (especially using non-free software), etc.&lt;/p&gt;
&lt;p&gt;Everything that requires people to basically interact with the project in a synchronous manner for a period of time will put constraints on them that can make them uncomfortable.&lt;/p&gt;
&lt;p&gt;The best medium is still e-mail and asynchronous derivative (bug trackers, etc), as it is asynchronous and allow people to work at their own pace at their own time.&lt;/p&gt;
&lt;h2&gt;Not having an (implicit) CoC&lt;/h2&gt;
&lt;p&gt;Codes of conduct seem to be a trendy topic (and a touchy subject), as more and more communities are opening to a wilder audience than they used to be – which is great.&lt;/p&gt;
&lt;p&gt;Actually, all communities have a code of conduct, being written with black ink or being carried in everyone&apos;s mind unconsciously. Its form is a matter of community size and culture.&lt;/p&gt;
&lt;p&gt;Now, depending on the size of your community and how you feel comfortable applying it, you may want to have it composed in a document, e.g. like &lt;a href=&quot;https://www.debian.org/code_of_conduct&quot;&gt;Debian did&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Having a code of conduct does not transform your whole project community magically into a bunch of carebears following its guidance. But it provides an interesting point you can refer to as soon as you need. It can help throwing it at some people, to indicate that their behavior is not welcome in the project, and somehow, ease their potential exclusion – even if nobody wants to go that far generally, and that&apos;s it&apos;s rarely that useful.&lt;/p&gt;
&lt;p&gt;I don&apos;t think it&apos;s mandatory to have such a paper on smaller projects. But you have to keep in mind that the implicit code of conduct will be derived from &lt;em&gt;your&lt;/em&gt; own behavior. The way your leader(s) will communicate with others will set the entire social mood of the project. Do not underestimate that.&lt;/p&gt;
&lt;p&gt;When we started the &lt;a href=&quot;http://launchpad.net/ceilometer&quot;&gt;Ceilometer&lt;/a&gt; project, we implicitly followed the &lt;a href=&quot;https://www.openstack.org/legal/community-code-of-conduct/&quot;&gt;OpenStack Code of Conduct&lt;/a&gt; before it even existed, and probably set the bar a little higher. Being nice, welcoming and open-minded, we achieved a descent score of diversity, having up to 25% of our core team being women – way above the current ratio in OpenStack and most open source projects!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/friends-beach.jpg&quot; alt=&quot;friends-beach&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Making people not English native feeling like outsider&lt;/h2&gt;
&lt;p&gt;It&apos;s quite important to be aware of that the vast majority of free software project out there are using English as the common language of communication. It makes a lot of sense: it&apos;s a commonly spoken language, and it seems to do the job correctly.&lt;/p&gt;
&lt;p&gt;But a large part of the hackers out there are not native English speakers. Many are not able to speak English fluently. That means the rate at which they can communicate and run a conversation might be very low, which can make some people frustrated, especially native English speaker.&lt;/p&gt;
&lt;p&gt;The principal demonstration of this phenomena can be seen in social events (e.g. conferences) where people are debating. It can be very hard for people to explain their thoughts in English and to communicate properly at a decent rate, making the conversation and the transmission of ideas slow. The worst thing that one can see in this context is an English native speaker cutting people off and ignoring them, just because they are talking too slowly. I do understand that it can be frustrating, but the problem here is not the non-native English speaking, it&apos;s the medium being used that does not make your fellow on the same level of everyone by moving the conversation orally.&lt;/p&gt;
&lt;p&gt;To a lesser extent, the same applies to IRC meetings, which are by relatively synchronous. Completely asynchronous media do not have this flaw, that&apos;s why they should also be preferred in my opinion.&lt;/p&gt;
&lt;h2&gt;No vision, no delegation&lt;/h2&gt;
&lt;p&gt;Two of the most commonly encountered mistakes in open source projects: seeing the maintainer struggling with the growth of its project while having people trying to help.&lt;/p&gt;
&lt;p&gt;Indeed, when the flow of contributor starts coming in, adding new features, asking for feedback and directions, some maintainers choke and don&apos;t know how to respond. That ends up frustrating contributors, which therefore may simply vanish.&lt;/p&gt;
&lt;p&gt;It&apos;s important to have a vision for your project and communicate it. Make it clear for contributors what you want or don&apos;t want in your project. Transferring that in a clear (and non-aggressive, please) manner, is a good way of lowering the friction between contributors. They&apos;ll pretty soon know if they want to join your ship or not, and what to expect. So be a good captain.&lt;/p&gt;
&lt;p&gt;If they chose to work with you and contribute, you should start trusting them as soon as you can and delegate some of your responsibilities. This can be anything that you used to do: review patches targeting some subsystem, fixing bugs, writing docs. Let people own an entire part of the project so they feel responsible and care about it as much as you do. Doing the opposite, which is being a control-freak, is the best shot at staying alone with your open source software.&lt;/p&gt;
&lt;p&gt;And no project is going to grow and be successful that way.&lt;/p&gt;
&lt;p&gt;In 2009, when Uli Schlachter sent &lt;a href=&quot;http://article.gmane.org/gmane.comp.window-managers.awesome.devel/1746/match=uli+schlachter&quot;&gt;his first patch to awesome&lt;/a&gt;, this was more work for me. I had to review this patch, and I was already pretty busy designing the new versions of awesome and doing my day job! Uli&apos;s work was not perfect, and I had to fix it myself. More work. And what did I do? A few minutes later, I &lt;a href=&quot;http://article.gmane.org/gmane.comp.window-managers.awesome.devel/1747/match=uli+schlachter&quot;&gt;replied to him&lt;/a&gt; with a clear plan of what he should do and what I thought about his work.&lt;/p&gt;
&lt;p&gt;In response, Uli sent patches and improved the project. Do you know what Uli does today? He manages the awesome window manager project since 2010 instead of me. I managed to transmit my vision, delegate, and then retired!&lt;/p&gt;
&lt;h2&gt;Non-recognition of contributions&lt;/h2&gt;
&lt;p&gt;People contribute in different ways, and it&apos;s not always code. There&apos;s a lot of things around a free software projects: documentation, bug triage, user support, user experience design, communication, translation…&lt;/p&gt;
&lt;p&gt;It took a while for example to &lt;a href=&quot;http://debian.org&quot;&gt;Debian&lt;/a&gt; to recognize that their translators could have the status of Debian Developer. &lt;a href=&quot;http://openstack.org&quot;&gt;OpenStack&lt;/a&gt; is working in the same direction by trying to &lt;a href=&quot;https://wiki.openstack.org/wiki/NonATCRecognition&quot;&gt;recognize non-technical contributions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As soon as your project starts attributing badges to some people and creating classes of different members in the community, you should be very careful that you don&apos;t forget anyone. That&apos;s the easiest road to losing contributors along the road.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/heart-sign.jpg&quot; alt=&quot;heart-sign&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Don&apos;t forget to be thankful&lt;/h2&gt;
&lt;p&gt;This whole list has been inspired by many years of open source hacking and free software contributions. Everyone&apos;s experience and feeling might be different, or malpractice may have been seen under different forms. Let me know and if there&apos;s any other point that you encountered and blocked you to contribute to open source projects!&lt;/p&gt;
</content:encoded></item><item><title>Gnocchi talk at the Paris Monitoring Meetup #6</title><link>https://julien.danjou.info/blog/paris-monitoring-6-gnocchi/</link><guid isPermaLink="true">https://julien.danjou.info/blog/paris-monitoring-6-gnocchi/</guid><description>Last week was the sixth edition of the Paris Monitoring Meetup, where I was invited as a speaker to present and talk about Gnocchi.</description><pubDate>Fri, 27 May 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last week was the sixth edition of the &lt;a href=&quot;http://www.meetup.com/Paris-Monitoring/events/230515751/&quot;&gt;Paris Monitoring Meetup&lt;/a&gt;, where I was invited as a speaker to present and talk about &lt;a href=&quot;http://gnocchi.xyz&quot;&gt;Gnocchi&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/paris-monitoring.png&quot; alt=&quot;paris-monitoring&quot; /&gt;&lt;/p&gt;
&lt;p&gt;There was around 50 persons in the room, listening to my presentation of Gnocchi.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/jd-gnocchi-paris-monitoring-meetup-6.jpg&quot; alt=&quot;jd-gnocchi-paris-monitoring-meetup-6&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The talk went fine and I had a few interesting questions and feedback. One interesting point that keeps coming when talking about Gnocchi, is its OpenStack label, which scares away a lot of people. We definitely need to continue explaining that the project work stand-alone has a no dependency on OpenStack, just a great integration with it.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;http://www.monitoring-fr.org/&quot;&gt;Monitoring-fr&lt;/a&gt; organization also &lt;a href=&quot;http://www.monitoring-fr.org/2016/05/meetup-paris-monitoring-6-interview-de-julien-danjou-pour-gnocchi-metric-as-a-service/&quot;&gt;interviewed me&lt;/a&gt; after the meetup about Gnocchi. The interview is in French, obviously. I talk about Gnocchi, what it does, how it does it and why we started the project a couple of years ago. Enjoy, and let me know what you think!&lt;/p&gt;
</content:encoded></item><item><title>The Hacker&apos;s Guide to Python 3rd edition is out</title><link>https://julien.danjou.info/blog/the-hacker-guide-to-python-third-edition/</link><guid isPermaLink="true">https://julien.danjou.info/blog/the-hacker-guide-to-python-third-edition/</guid><description>Exactly a year ago, I released the second edition of my book The Hacker&apos;s Guide to Python . One more time, it has been a wonderful release and I received a lot of amazing feedback from my readers all</description><pubDate>Wed, 04 May 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Exactly a year ago, I &lt;a href=&quot;https://julien.danjou.info/blog/the-hacker-guide-to-python-second-edition&quot;&gt;released the second edition of my book The Hacker&apos;s Guide to Python&lt;/a&gt;. One more time, it has been a wonderful release and I received a lot of amazing feedback from my readers all over this year.&lt;/p&gt;
&lt;p&gt;Since then, the book has been &lt;strong&gt;translated into 2 languages&lt;/strong&gt;: Korean and Chinese. A few thousands of copies has been distributed there, and I&apos;m very glad the book has been such a success. I&apos;m looking into getting it translated into more languages – don&apos;t hesitate to get in touch with me if you have any interesting connections in your country.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/thgtp-korean.jpg&quot; alt=&quot;thgtp-korean&quot; /&gt;&lt;/p&gt;
&lt;p&gt;For those who still don&apos;t know about this guide, that I first released a couple of years ago, let me sum up by saying it&apos;s &lt;strong&gt;the Python book that I always wanted to read&lt;/strong&gt;, never found, and finally wrote. It does not cover the basics of the language, but deals with concrete problems, best practice and some of the languages internals.&lt;/p&gt;
&lt;p&gt;It includes content about unit testing, methods, decorators, AST, distribution, documentation, functional programming, scaling, Python 3, etc. All of that made it pretty &lt;strong&gt;successful&lt;/strong&gt;! It comes with awesome &lt;strong&gt;9 interviews&lt;/strong&gt; that I realized with some of my fellow experienced Python hackers and developers!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/thgtp-v3-photo-stack.jpg&quot; alt=&quot;thgtp-v3-photo-stack&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In that &lt;strong&gt;3rd edition&lt;/strong&gt;, there is, like in each new edition, a few fixes on code, typos, etc. I guess books need a lot of time to become perfect! I also updated some of the content: things evolved a bit since I last revised the content a year ago. Finally, a new chapter about timestamps handling and timezone has made his appearance too.&lt;/p&gt;
&lt;p&gt;If you didn&apos;t get the book yet, it&apos;s time to go &lt;a href=&quot;https://thehackerguidetopython.com&quot;&gt;check it out&lt;/a&gt; and use the coupon &lt;strong&gt;THGTP3LAUNCH&lt;/strong&gt; to get 20 % off during the next 48 hours!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/the-hacker-guide-to-python-darken-v2-1.png&quot; alt=&quot;the-hacker-guide-to-python-darken-v2-1&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>OpenStack Summit Newton from a Telemetry point of view</title><link>https://julien.danjou.info/blog/openstack-summit-newton-austin-telemetry/</link><guid isPermaLink="true">https://julien.danjou.info/blog/openstack-summit-newton-austin-telemetry/</guid><description>It&apos;s again that time of the year, where we all fly out to a different country to chat about OpenStack and what we&apos;ll do during the next 6 months.</description><pubDate>Mon, 02 May 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It&apos;s again that time of the year, where we all fly out to a different country to chat about OpenStack and what we&apos;ll do during the next 6 months. This time, it was in &lt;a href=&quot;https://en.wikipedia.org/wiki/Austin,_Texas&quot;&gt;Austin, TX&lt;/a&gt; and we chatted about the new Newton release that will be out in October.&lt;/p&gt;
&lt;p&gt;As the &lt;em&gt;Project Team Leader&lt;/em&gt; for the Telemetry project, I set up and animated the week for our team. We had 9 discussion slots of 40 minutes assigned, but finally only used 8. We also, somehow, canceled the contributor team meet-up on the last day, as only a few of us developers were there and available.&lt;/p&gt;
&lt;p&gt;We took &lt;a href=&quot;https://wiki.openstack.org/wiki/Design_Summit/Newton/Etherpads#Telemetry&quot;&gt;a few notes in our Etherpads&lt;/a&gt;, but I think most of them were pretty sparse, as there was nothing really important we talked about. Actually, many topics were already discussed and covered 6 months ago in Tokyo during the previous summit. We just did not have time to implement everything we wanted, so talking over it again would not have been of a great help.&lt;/p&gt;
&lt;h2&gt;Reference architecture&lt;/h2&gt;
&lt;p&gt;Unfortunately, nor Gordon Chung nor the &lt;a href=&quot;https://osic.org/&quot;&gt;OpenStack Innovation Center&lt;/a&gt; had time to run the tests and benchmarks they wanted to run before the summit. We still discussed their plan to run tests and benchmark of the whole Telemetry suite (Ceilometer, Gnocchi &amp;amp; Aodh). They should run their tests for 3 weeks, no more, in a few weeks. The window to run tests being narrow, they want to be sure they are prepared, and will reach to us for help, ideas, and validation.&lt;/p&gt;
&lt;p&gt;I&apos;ve also requested them to, if possible, provide us some profiling (e.g. cProfile) data so we can have better knowledge of the area we can optimize.&lt;/p&gt;
&lt;h2&gt;Gnocchi, next steps&lt;/h2&gt;
&lt;p&gt;This session was particularly smooth since most people in the room were not up-to-date with Gnocchi 2.1. Some people expressed concerned about the InfluxDB driver removal, though they were not aware of the bugs it had, and that Gnocchi was actually performing better – so they may very likely be testing Gnocchi directly instead.&lt;/p&gt;
&lt;p&gt;No particular fancy feature was requested, only a few bugs and ideas noted on Launchpad were discussed.&lt;/p&gt;
&lt;h2&gt;Enhancing Ceilometer polling&lt;/h2&gt;
&lt;p&gt;This session was not particularly productive, as everything was we wanted to discuss was already on the Etherpad from… Tokyo, 6 months ago. It turns out nobody had time to pursue this project, so we&apos;ll see what happens. There&apos;s definitely some work to do to pursue our goal of splitting the pipeline definition into smaller files.&lt;/p&gt;
&lt;h2&gt;Aodh roadmap &amp;amp; improvements&lt;/h2&gt;
&lt;p&gt;First, we decided to definitely kill the combination alarm in the future, in favor of the new composite alarms definition that we like better.&lt;/p&gt;
&lt;p&gt;We should switch to &lt;a href=&quot;http://docs.openstack.org/developer/python-openstackclient/&quot;&gt;OpenStackClient&lt;/a&gt; in the future for &lt;a href=&quot;http://docs.openstack.org/developer/python-aodhclient/&quot;&gt;aodhclient&lt;/a&gt;. The OSC team indicated they are willing to provide a way to keep the &quot;aodh&quot; CLI command on its own, which is something that blocked us to move to OSC.&lt;/p&gt;
&lt;p&gt;A bunch of people indicated that had support for alarms CRUD in the Horizon dashboard. They should work together with the Horizon team to complete what has been started in Horizon recently to add Aodh support.&lt;/p&gt;
&lt;h2&gt;Ceilometer splitting&lt;/h2&gt;
&lt;p&gt;A year ago, we decided to split Ceilometer and its alarm feature: Aodh was born. We did discuss doing it again 6 months ago, but nothing happened as we already had so many stuff on our plate.&lt;/p&gt;
&lt;p&gt;As far as I&apos;m concerned, I think it&apos;s now time to split some Ceilometer functionality again, so I&apos;m going to do that this time with the event part. Gordon found a name, and this new project will be named &lt;em&gt;Panko&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;We have then discussed our documentation. Users present in the room were particularly happy with the Gnocchi policy that we apply since the beginning: no doc = no merge of your patch. The consensus is to move forward on this policy for all Telemetry projects, especially since it&apos;s now clear that the documentation team is not going to help us more. Ildikó, our documentation&lt;br /&gt;
wizard, will take care of making some links between the official OpenStack documentation and our projects, avoid content duplication.&lt;/p&gt;
&lt;p&gt;For this cycle, my personal plan is to document Aodh up to roughly 80 %, and then force that policy on newly implemented changes.&lt;/p&gt;
&lt;h2&gt;Events management&lt;/h2&gt;
&lt;p&gt;The event management part of Ceilometer and API (soon to be split in its own project as stated above) was discussed in this session. Nothing really exciting coming here, as nobody is willing to enhance it for now. Which, again, makes it a great candidate for splitting it out of Ceilometer.&lt;/p&gt;
&lt;h2&gt;Vitrage&lt;/h2&gt;
&lt;p&gt;The last session was dedicated to &lt;a href=&quot;https://wiki.openstack.org/wiki/Vitrage&quot;&gt;Vitrage&lt;/a&gt;, a root cause analysis tool built on OpenStack. The Vitrage team had a few features that they wanted to see in Aodh, so we discussed that at length. Notably, more support for sending notifications on events (alarm creation, deletion…) should be added in this next release.&lt;/p&gt;
&lt;p&gt;Also, a new alarm type that would be entirely managed and triggered over HTTP would be very useful for external projects such as Vitrage. We&apos;ll try to make that happen during this cycle too.&lt;/p&gt;
&lt;h2&gt;Talks&lt;/h2&gt;
&lt;p&gt;There were a few interesting talks about our telemetry projects during this summit, among other I highly recommend watching:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=W5KT5GJKJw8&quot;&gt;OpenStack Ceilometer with Gnocchi and Aodh Feature&lt;/a&gt;, where Amol and Paul from Ericsson explain what Gnocchi and Aodh do and how they work, and then help people deploy it on their lab.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=BdebhsBFEJs&quot;&gt;DPDK, Collectd &amp;amp; Ceilometer The Missing Link&lt;/a&gt;, where Ryota Mibu, one of the contributor to Aodh explains why he implemented the event alarm feature&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=-K8NI38LPtU&quot;&gt;Showback &amp;amp; Chargeback!! OpenStack Gnocchi + Cloudkitty as a Whole Billing System&lt;/a&gt;, where Maximiliano Venesio (Nubeliu) and Stéphane Albert (Objectif Libre) talk about how they built an amazing scalable billing solution using &lt;a href=&quot;http://gnocchi.xyz&quot;&gt;Gnocchi&lt;/a&gt; and &lt;a href=&quot;https://wiki.openstack.org/wiki/CloudKitty&quot;&gt;CloudKitty&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=0Q8pfbwxMb8&quot;&gt;Using Ceilometer Data for Effective Witch-Hunting&lt;/a&gt;, where Mike explain how Overstock.com leveraged Ceilometer to track anomalies in their cloud.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of this should keep me and the team busy for the next cycle. If you have any question about what has been discussed or the future of our projects, don&apos;t hesitate to leave a comment or ask us on the &lt;a href=&quot;http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev&quot;&gt;OpenStack development mailing list&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Gnocchi 2.1 release</title><link>https://julien.danjou.info/blog/gnocchi-2-1-release/</link><guid isPermaLink="true">https://julien.danjou.info/blog/gnocchi-2-1-release/</guid><description>A little less than 2 months after our latest major release, here is the new minor version of Gnocchi, stamped 2.1.0.</description><pubDate>Wed, 13 Apr 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A little less than 2 months after our latest major release, here is the new minor version of Gnocchi, stamped &lt;a href=&quot;https://launchpad.net/gnocchi/2.1/2.1.0&quot;&gt;2.1.0&lt;/a&gt;. It was a smooth release, but with one major feature implemented by my fellow fantastic developer Mehdi Abaakouk: the ability to create resource types dynamically.&lt;/p&gt;
&lt;h2&gt;Resource types REST API&lt;/h2&gt;
&lt;p&gt;This new version of Gnocchi offers the long-awaited ability to create resource types dynamically. What does that mean? Well, until version 2.0, the resources that you were able to create in Gnocchi had a particular type that was defined in the code: instance, volume, SNMP host, Swift account, etc. All of them were tied to OpenStack, since it was our primary use case.&lt;/p&gt;
&lt;p&gt;Now, &lt;a href=&quot;http://gnocchi.xyz/rest.html#resource-types&quot;&gt;the API allows to create resource types dynamically&lt;/a&gt;. This means you can create your own custom types to describe your own architecture. You then can exploit the same features that were offered before: history of your resources, searching through them, associating metrics, etc!&lt;/p&gt;
&lt;h2&gt;Performances improvement&lt;/h2&gt;
&lt;p&gt;We did some profiling on Gnocchi, and some benchmarks, and with the help of my fellow developer Gordon Chung, improved the metric performances.&lt;/p&gt;
&lt;p&gt;The API speed improved a bit, and I&apos;ve measured the Gnocchi API endpoint of being able to ingest up to 190k measures/s with only one node (the same as used in my &lt;a href=&quot;https://julien.danjou.info/blog/gnocchi-benchmarks&quot;&gt;previous benchmark&lt;/a&gt;) using &lt;a href=&quot;https://uwsgi-docs.readthedocs.org/&quot;&gt;uwsgi&lt;/a&gt;, so a 50 % improvement. The time required to compute aggregation on new measures is now also metered and displayed in the &lt;code&gt;gnocchi-metricd&lt;/code&gt; log in debug mode. Handy to have an idea of how fast your measures are treated.&lt;/p&gt;
&lt;h2&gt;Ceph backend optimization&lt;/h2&gt;
&lt;p&gt;The Ceph back-end has been improved again by Mehdi. We&apos;re now relying on OMAP rather than xattr for finer grained control and better performance.&lt;/p&gt;
&lt;p&gt;We already have a few new features being prepared for our next release, so stay tuned! And if you have any suggestion, feel free to say a word.&lt;/p&gt;
</content:encoded></item><item><title>Pifpaf, or how to run any daemon briefly</title><link>https://julien.danjou.info/blog/pifpaf-a-tool-to-run-daemon-briefly/</link><guid isPermaLink="true">https://julien.danjou.info/blog/pifpaf-a-tool-to-run-daemon-briefly/</guid><description>There&apos;s a lot of situation where you end up needing a software deployed temporarily. This can happen when testing something manually, when running a script or when launching a test suite.  Indeed, man</description><pubDate>Fri, 08 Apr 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;There&apos;s a lot of situation where you end up needing a software deployed temporarily. This can happen when testing something manually, when running a script or when launching a test suite.&lt;/p&gt;
&lt;p&gt;Indeed, many applications need to use and interconnect with external software: a RDBMS (&lt;a href=&quot;http://postgressql.org&quot;&gt;PostgreSQL&lt;/a&gt;, &lt;a href=&quot;http://mysql.org&quot;&gt;MySQL&lt;/a&gt;…), a cache (&lt;a href=&quot;http://memcached.org&quot;&gt;memcached&lt;/a&gt;, &lt;a href=&quot;http://redis.io&quot;&gt;Redis&lt;/a&gt;…) or any other external component. This tends to make more difficult running a software (or its test suite). If you want to rely on this component being installed and deployed, you end up needing a full environment set-up and properly configured to run your tests. Which is discouraging.&lt;/p&gt;
&lt;p&gt;The different &lt;a href=&quot;http://openstack.org&quot;&gt;OpenStack&lt;/a&gt; projects I work on ended up pretty soon spawning some of their back-ends temporarily to run their tests. Some of those unit tests somehow became entirely what you would call functional or integration tests. But that&apos;s just a name. In the end, what we ended up doing is testing that the software was really working. And there&apos;s no better way doing that than talking to a real PostgreSQL instance rather than mocking every call.&lt;/p&gt;
&lt;h2&gt;Pifpaf to the rescue&lt;/h2&gt;
&lt;p&gt;To solve that issue, I created a new tool, named &lt;em&gt;&lt;a href=&quot;https://github.com/jd/pifpaf&quot;&gt;Pifpaf&lt;/a&gt;&lt;/em&gt;. &lt;em&gt;Pifpaf&lt;/em&gt; eases the run of any daemon in a test mode for a brief moment, before making it disappear completely. It&apos;s pretty easy to install as &lt;a href=&quot;http://pypi.python.org/pypi/pifpaf&quot;&gt;it is available on PyPI&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ pip install pifpaf
Collecting pifpaf
[…]
Installing collected packages: pifpaf
Successfully installed pifpaf-0.0.7
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can then use it to run any of the listed daemons:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ pifpaf list
+---------------+
| Daemons       |
+---------------+
| redis         |
| postgresql    |
| mongodb       |
| zookeeper     |
| aodh          |
| influxdb      |
| ceph          |
| elasticsearch |
| etcd          |
| mysql         |
| memcached     |
| rabbitmq      |
| gnocchi       |
+---------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Pifpaf&lt;/em&gt; accepts any shell command line to execute after its arguments:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ pifpaf run postgresql -- psql
Expanded display is used automatically.
Line style is unicode.
SET
psql (9.5.2)
Type &quot;help&quot; for help.

template1=# \l
                              List of databases
   Name    │ Owner │ Encoding │   Collate   │    Ctype    │ Access privileges
───────────┼───────┼──────────┼─────────────┼─────────────┼───────────────────
 postgres  │ jd    │ UTF8     │ en_US.UTF-8 │ en_US.UTF-8 │
 template0 │ jd    │ UTF8     │ en_US.UTF-8 │ en_US.UTF-8 │ =c/jd            ↵
           │       │          │             │             │ jd=CTc/jd
 template1 │ jd    │ UTF8     │ en_US.UTF-8 │ en_US.UTF-8 │ =c/jd            ↵
           │       │          │             │             │ jd=CTc/jd
(3 rows)

template1=# create database foobar;
CREATE DATABASE
template1=# \l
                              List of databases
   Name    │ Owner │ Encoding │   Collate   │    Ctype    │ Access privileges
───────────┼───────┼──────────┼─────────────┼─────────────┼───────────────────
 foobar    │ jd    │ UTF8     │ en_US.UTF-8 │ en_US.UTF-8 │
 postgres  │ jd    │ UTF8     │ en_US.UTF-8 │ en_US.UTF-8 │
 template0 │ jd    │ UTF8     │ en_US.UTF-8 │ en_US.UTF-8 │ =c/jd            ↵
           │       │          │             │             │ jd=CTc/jd
 template1 │ jd    │ UTF8     │ en_US.UTF-8 │ en_US.UTF-8 │ =c/jd            ↵
           │       │          │             │             │ jd=CTc/jd
(4 rows)

template1=# \q
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What &lt;em&gt;pifpaf&lt;/em&gt; does is that it runs the different commands needed to create a new PostgreSQL cluster and then run PostgreSQL on a temporary port for you. So your &lt;em&gt;psql&lt;/em&gt; session actually connects to a temporary PostgreSQL server, that is trashed as soon as you quit &lt;em&gt;psql&lt;/em&gt;. And all of that in less than 10 seconds, without the use of any virtualization or container technology!&lt;/p&gt;
&lt;p&gt;You can see what it does in detail using the &lt;em&gt;debug&lt;/em&gt; mode:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ pifpaf --debug run mysql $SHELL
DEBUG: pifpaf.drivers: executing: [&apos;mysqld&apos;, &apos;--initialize-insecure&apos;, &apos;--datadir=/var/folders/7k/pwdhb_mj2cv4zyr0kyrlzjx40000gq/T/tmpkut9bg&apos;]
DEBUG: pifpaf.drivers: executing: [&apos;mysqld&apos;, &apos;--datadir=/var/folders/7k/pwdhb_mj2cv4zyr0kyrlzjx40000gq/T/tmpkut9bg&apos;, &apos;--pid-file=/var/folders/7k/pwdhb_mj2cv4zyr0kyrlzjx40000gq/T/tmpkut9bg/mysql.pid&apos;, &apos;--socket=/var/folders/7k/pwdhb_mj2cv4zyr0kyrlzjx40000gq/T/tmpkut9bg/mysql.socket&apos;, &apos;--skip-networking&apos;, &apos;--skip-grant-tables&apos;]
DEBUG: pifpaf.drivers: executing: [&apos;mysql&apos;, &apos;--no-defaults&apos;, &apos;-S&apos;, &apos;/var/folders/7k/pwdhb_mj2cv4zyr0kyrlzjx40000gq/T/tmpkut9bg/mysql.socket&apos;, &apos;-e&apos;, &apos;CREATE DATABASE test;&apos;]
[…]
$ exit
[…]
DEBUG: pifpaf.drivers: mysqld output: 2016-04-08T08:52:04.202143Z 0 [Note] InnoDB: Starting shutdown...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Pifpaf&lt;/em&gt; also supports my pet project &lt;a href=&quot;http://launchpad.net/gnocchi&quot;&gt;Gnocchi&lt;/a&gt;, so you can run and try that timeseries database in a snap:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ pifpaf run gnocchi $SHELL
$ gnocchi metric create
+------------------------------------+-----------------------------------------------------------------------+
| Field                              | Value                                                                 |
+------------------------------------+-----------------------------------------------------------------------+
| archive_policy/aggregation_methods | std, count, 95pct, min, max, sum, median, mean                        |
| archive_policy/back_window         | 0                                                                     |
| archive_policy/definition          | - points: 12, granularity: 0:05:00, timespan: 1:00:00                 |
|                                    | - points: 24, granularity: 1:00:00, timespan: 1 day, 0:00:00          |
|                                    | - points: 30, granularity: 1 day, 0:00:00, timespan: 30 days, 0:00:00 |
| archive_policy/name                | low                                                                   |
| created_by_project_id              | admin                                                                 |
| created_by_user_id                 | admin                                                                 |
| id                                 | ff825d33-c8c8-46d4-b696-4b1e8f84a871                                  |
| name                               | None                                                                  |
| resource/id                        | None                                                                  |
+------------------------------------+-----------------------------------------------------------------------+
$ exit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And it takes less than 10 seconds to launch Gnocchi on my laptop using &lt;em&gt;pifpaf&lt;/em&gt;. I&apos;m then able to play with the &lt;code&gt;gnocchi&lt;/code&gt; command line tool. It&apos;s by far faster than using OpenStack &lt;a href=&quot;http://devstack.org&quot;&gt;devstack&lt;/a&gt; to deloy everything the software.&lt;/p&gt;
&lt;h2&gt;Using &lt;em&gt;pifpaf&lt;/em&gt; with your test suite&lt;/h2&gt;
&lt;p&gt;We leverage &lt;em&gt;Pifpaf&lt;/em&gt; in several of our OpenStack telemetry related projects now, and even in &lt;a href=&quot;http://launchpad.net/tooz&quot;&gt;tooz&lt;/a&gt;. For example, to run unit/functional tests with a &lt;em&gt;memcached&lt;/em&gt; server available, a &lt;code&gt;tox.ini&lt;/code&gt; file should like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[testenv:py27-memcached]
commands = pifpaf run memcached -- python setup.py testr
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The tests can then use the environment variable &lt;code&gt;PIFPAF_MEMCACHED_PORT&lt;/code&gt; to connect to &lt;em&gt;memcached&lt;/em&gt; and run tests using it. As soon as the tests are finished, &lt;em&gt;memcached&lt;/em&gt; is killed by &lt;em&gt;pifpaf&lt;/em&gt; and the temporary data are trashed.&lt;/p&gt;
&lt;p&gt;We move a few OpenStack projects to using &lt;em&gt;Pifpaf&lt;/em&gt; already, and I&apos;m planning to make use of it in a few more. My fellow developer &lt;a href=&quot;http://sileht.net&quot;&gt;Mehdi Abaakouk&lt;/a&gt; added support for &lt;a href=&quot;http://rabbitmq.com&quot;&gt;RabbitMQ&lt;/a&gt; in &lt;em&gt;Pifpaf&lt;/em&gt; and &lt;a href=&quot;https://review.openstack.org/#/c/301771&quot;&gt;added support for more advanced tests&lt;/a&gt; in &lt;a href=&quot;http://launchpad.net/oslo.messaging&quot;&gt;oslo.messaging&lt;/a&gt; (such as failure scenarios) using &lt;em&gt;Pifpaf&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Pifpaf&lt;/em&gt; is a very small and handy tool. Give it a try and let me know how it works for you!&lt;/p&gt;
</content:encoded></item><item><title>The OpenStack Schizophrenia</title><link>https://julien.danjou.info/blog/openstack-schizophrenia/</link><guid isPermaLink="true">https://julien.danjou.info/blog/openstack-schizophrenia/</guid><description>When I started contributing to OpenStack, almost five years ago, it was a small ecosystem. There were no foundation, a handful of projects and you could understand the code base in a few days.</description><pubDate>Wed, 30 Mar 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When I started contributing to &lt;a href=&quot;http://openstack.org&quot;&gt;OpenStack&lt;/a&gt;, almost five years ago, it was a small ecosystem. There were no foundation, a handful of projects and you could understand the code base in a few days.&lt;/p&gt;
&lt;p&gt;Fast forward 2016, and it is a totally different beast. The project grew to &lt;a href=&quot;http://governance.openstack.org/reference/projects/index.html&quot;&gt;no less than 54 teams&lt;/a&gt;, each team providing one or more deliverable. For example, the Nova and Swift team each one produces one service and its client, whereas the Telemetry team produces 3 services and 3 different clients.&lt;/p&gt;
&lt;p&gt;In 5 years, OpenStack went to a few &lt;a href=&quot;https://en.wikipedia.org/wiki/Infrastructure_as_a_service&quot;&gt;IaaS&lt;/a&gt; projects, to 54 different teams tackling different areas related to cloud computing. Once upon a time, OpenStack was all about starting some virtual machines on a network, backed by images and volumes. Nowadays, it&apos;s also about orchestrating your network deployment over containers, while managing your application life-cycle using a database service, everything being metered and billed for.&lt;/p&gt;
&lt;p&gt;This exponential growth has been made possible with the decision of the &lt;a href=&quot;http://governance.openstack.org/reference/charter.html&quot;&gt;OpenStack Technical Committee&lt;/a&gt; to open the gates with &lt;a href=&quot;http://governance.openstack.org/resolutions/20141202-project-structure-reform-spec.html&quot;&gt;the project structure reform voted at the end of 2014&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This amendment suppresses the old OpenStack model of &quot;integrated projects&quot; (i.e. Nova, Glance, Swift…). The big tent, as it&apos;s called, allowed OpenStack to land new projects every month, growing from the 20 project teams of December 2014 to the 54 we have today – multiplying the number of projects by 2.7 in a little more than a year.&lt;/p&gt;
&lt;p&gt;Amazing growth, right?&lt;/p&gt;
&lt;p&gt;And this was clearly a good change. I sat at the Technical Committee in 2013, when projects were trying to apply to be &quot;integrated&quot;, after Ceilometer and Heat were. It was painful to see how the Technical Committee was trying to assess whether new projects should be brought in or not.&lt;/p&gt;
&lt;p&gt;But what I notice these days, is how OpenStack is still stuck between its old and new models. On one side, it accepted a lot of new teams, but on the other side, many are considered as second-class citizens. Efforts are made to continue to build an OpenStack project that does not exist anymore.&lt;/p&gt;
&lt;p&gt;For example, there is a team trying to define what&apos;s OpenStack core, named &lt;a href=&quot;https://github.com/openstack/defcore&quot;&gt;DefCore&lt;/a&gt;. That is looking to define which projects are, somehow, actually OpenStack. This leads to weird situations, &lt;a href=&quot;http://lists.openstack.org/pipermail/openstack-dev/2016-March/090214.html&quot;&gt;such as having non-DefCore projects seeing their doc rejected from installation guides&lt;/a&gt;.&lt;br /&gt;
Again, &lt;a href=&quot;http://lists.openstack.org/pipermail/openstack-dev/2016-March/090231.html&quot;&gt;I reiterated my proposal&lt;/a&gt; to publish documentation as part of each project code to solve that dishonest situation and put everything on a level playing field&lt;/p&gt;
&lt;p&gt;Some cross-projects specs are also pushed without implication of all OpenStack projects. For example, The &lt;a href=&quot;https://specs.openstack.org/openstack/openstack-specs/specs/deprecate-cli.html&quot;&gt;deprecate-cli&lt;/a&gt; spec which proposes to deprecate command-line interface tools proposed by each project had a lot of sense in the old OpenStack sense, where the goal was to build a unified and ubiquitous cloud platform. But when you now have tens of projects with largely different scopes, this start making less sense. Still, this spec was merged by the OpenStack Technical Committee this cycle. Keystone is the first project to proudly force users to rely on&lt;br /&gt;
&lt;a href=&quot;http://docs.openstack.org/developer/python-openstackclient/&quot;&gt;openstack-client&lt;/a&gt;, removing its old &lt;code&gt;keystone&lt;/code&gt; command line tool. I find it odd to push that specs when it&apos;s pretty clear that some projects (e.g. Swift, Gnocchi…) have no intention to go down that path.&lt;/p&gt;
&lt;p&gt;Unfortunately, most specs pushed by the Technical Committee are in the realm of wishful thinking. It somehow makes sense, since only a few of the members are actively contributing to OpenStack projects, and they can&apos;t by themselves implement all of that magically. But OpenStack is no exception in the free software world and remains a do-ocracy.&lt;/p&gt;
&lt;p&gt;There is good cross-project content in OpenStack, such as &lt;a href=&quot;https://wiki.openstack.org/wiki/API_Working_Group&quot;&gt;the API working group&lt;/a&gt;. While the work done should probably not be OpenStack specific, there&apos;s a lot that teams have learned by building various HTTP REST API with different frameworks. Compiling this knowledge and offering it as a guidance to various teams is a great help.&lt;/p&gt;
&lt;p&gt;My fellow developer &lt;a href=&quot;https://anticdent.org&quot;&gt;Chris Dent&lt;/a&gt; wrote a post about &lt;a href=&quot;https://anticdent.org/if-i-were-on-the-openstack-tc.html&quot;&gt;what he would do on the Technical Committee&lt;/a&gt;.&lt;br /&gt;
In this article, he points to a lot of the shortcomings I described here, and his confusion between OpenStack being a product or being a kit is quite understandable. Indeed, the message broadcasted by OpenStack is still very confusing after the big tent openness. There&apos;s no enough user experience improvement being done.&lt;/p&gt;
&lt;p&gt;The OpenStack Technical Committee election is opened for April 2016, and from what I read so far, many candidates are proposing to now clean up the big tent, kicking out projects that do not match certain criteria anymore. This is probably a good idea, there is some inactive project laying around. But I don&apos;t think that will be enough to solve the identity crisis that OpenStack is experiencing.&lt;/p&gt;
&lt;p&gt;So this is why, once again this cycle, I will throw my hat in the ring and submit my candidacy for OpenStack Technical Committee.&lt;/p&gt;
</content:encoded></item><item><title>Gnocchi 2.0 release</title><link>https://julien.danjou.info/blog/gnocchi-2-0-release/</link><guid isPermaLink="true">https://julien.danjou.info/blog/gnocchi-2-0-release/</guid><description>Gnocchi 2.0 is out with major new features including a Grafana datasource, Ceph storage driver, and a revamped REST API.</description><pubDate>Fri, 19 Feb 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A little more than 3 months after our latest minor release, here is the new major version of Gnocchi, stamped &lt;a href=&quot;https://launchpad.net/gnocchi/2.0/2.0.0&quot;&gt;2.0.0&lt;/a&gt;. It contains a lot of new and exciting features, and I&apos;d like to talk about some of them to celebrate!&lt;/p&gt;
&lt;p&gt;You may notice that this release happens in the middle of the OpenStack release cycle. Indeed, Gnocchi does not follow that 6-months cycle, and we release whenever our code is ready. That forces us to have a more iterative approach, less disruptive for other projects and allow us to achieve a higher velocity. Applying the good old mantra &lt;em&gt;release early, release often&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;This version features a large documentation update. Gnocchi is still the only OpenStack server project that implements a &quot;no doc, no merge&quot; policy, meaning any code must come with the documentation addition or change included in the patch. The full documentation is included in the source code and available online at &lt;a href=&quot;http://gnocchi.xyz/&quot;&gt;gnocchi.xyz&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Data split &amp;amp; compression&lt;/h2&gt;
&lt;p&gt;I&apos;ve already covered this change extensively in &lt;a href=&quot;https://julien.danjou.info/blog/gnocchi-carbonara-timeseries-compression&quot;&gt;my last blog about timeseries compression&lt;/a&gt;. Long story short, Gnocchi now splits timeseries archives in small chunks that are compressed, increasing speed and decreasing data size.&lt;/p&gt;
&lt;h2&gt;Measures batching support&lt;/h2&gt;
&lt;p&gt;Gnocchi now supports batching, which allow submitting several measures for different metric in a single request. This is especially useful in the context where your application tends to cache metrics for a while and is able to send them in a batch. Usage is &lt;a href=&quot;http://gnocchi.xyz/rest.html#measures-batching&quot;&gt;fully documented for the REST API&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Group by support in aggregation&lt;/h2&gt;
&lt;p&gt;One of the most demanded features was the ability to do measure aggregation no resource, using a group by type query. This is now possible using the &lt;a href=&quot;http://gnocchi.xyz/rest.html#aggregation-across-metrics&quot;&gt;new &lt;code&gt;groupby&lt;/code&gt; parameter to aggregation queries&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Ceph backend optimization&lt;/h2&gt;
&lt;p&gt;We improved the Ceph back-end a lot. Mehdi Abaakouk wrote a new Python binding for Ceph, called &lt;a href=&quot;https://github.com/sileht/pycradox&quot;&gt;Cradox&lt;/a&gt;, that is going to replace the current Python rados module in the subsequent Ceph releases. Gnocchi makes usage of this new module to speed things up, making the Ceph based driver really, really faster than before. We also implemented asynchronous data deletion, which improves performance a bit.&lt;/p&gt;
&lt;p&gt;The next step will be to run some new benchmarks &lt;a href=&quot;https://julien.danjou.info/blog/gnocchi-benchmarks&quot;&gt;like I did a few months ago&lt;/a&gt; and compare with the Gnocchi 1.3 series. Stay tuned!&lt;/p&gt;
</content:encoded></item><item><title>Timeseries storage and data compression</title><link>https://julien.danjou.info/blog/gnocchi-carbonara-timeseries-compression/</link><guid isPermaLink="true">https://julien.danjou.info/blog/gnocchi-carbonara-timeseries-compression/</guid><description>The first major version of the scalable timeserie database I work on, Gnocchi was a released a few months ago. In this first iteration, it took a rather naive approach to data storage.</description><pubDate>Mon, 15 Feb 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The first major version of the scalable timeserie database I work on, &lt;a href=&quot;http://gnocchi.xyz&quot;&gt;Gnocchi&lt;/a&gt; was a released a few months ago. In this first iteration, it took a rather naive approach to data storage. We had little ideas about if and how our distributed back-ends were going to be heavily used, so we stuck to the code of the first proof-of-concept written a couple of years ago.&lt;/p&gt;
&lt;p&gt;Recently we got more feedbacks from our users, ran a few &lt;a href=&quot;https://julien.danjou.info/blog/gnocchi-benchmarks&quot;&gt;benchmarks&lt;/a&gt;. That gave us enough feedback to start investigating in improving our storage strategy.&lt;/p&gt;
&lt;h2&gt;Data split&lt;/h2&gt;
&lt;p&gt;Up to Gnocchi 1.3, all data for a single metric are stored in a single gigantic file per aggregation method (&lt;em&gt;min&lt;/em&gt;, &lt;em&gt;max&lt;/em&gt;, &lt;em&gt;average&lt;/em&gt;…). This means that the file can grow to several megabytes in size, which make it slow to manipulate. For the next version of Gnocchi, our first work has been to rework that storage and split the data into smaller parts.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-carbonara-split.png&quot; alt=&quot;gnocchi-carbonara-split&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The diagram above shows how data are organized inside Gnocchi. Until version 1.3, there would have been only one file for each aggregation methods.&lt;/p&gt;
&lt;p&gt;In the upcoming 2.0 version, Gnocchi will split all these data into smaller parts, where each data split is stored in a file/object. This allows to manipulate smaller pieces of data and to increase the parallelism of the CRUD operations on the back-end – leading to large speed improvement.&lt;/p&gt;
&lt;p&gt;In order to split timeseries into several chunks, Gnocchi defines a maximum number of N points to keep per chunk, to limit their maximum size. It then defines a hash function that produces a non-unique key for any timestamp. It makes it easy to find in which chunk any timestamp should be stored or retrieved.&lt;/p&gt;
&lt;h2&gt;Data compression&lt;/h2&gt;
&lt;p&gt;Up to Gnocchi 1.3, the data stored for each metric is simply serialized using &lt;a href=&quot;http://msgpack.org&quot;&gt;msgpack&lt;/a&gt;, a fast and small serialization format. Though, this format does not provide any compression. That means that storing data points needs 8 bytes for a timestamp (64 bits timestamp with nanosecond precision) and 8 bytes for a value (64 bits double-precision floating-point), plus some overhead (extra information and &lt;em&gt;msgpack&lt;/em&gt; itself).&lt;/p&gt;
&lt;p&gt;After looking around on how to compress all these measures, I stumbled upon a paper from some &lt;a href=&quot;http://facebook&quot;&gt;Facebook&lt;/a&gt; engineers called about Gorilla, their in-memory timeserie database, entitled &quot;&lt;em&gt;&lt;a href=&quot;http://www.vldb.org/pvldb/vol8/p1816-teller.pdf&quot;&gt;Gorilla: A Fast, Scalable, In-Memory Time Series Database&lt;/a&gt;&lt;/em&gt;&quot;. For reference, part of this encoding is also used by &lt;a href=&quot;https://docs.influxdata.com/influxdb/v0.9/concepts/storage_engine/&quot;&gt;InfluxDB&lt;/a&gt; in its new storage engine.&lt;/p&gt;
&lt;p&gt;The first technique I implemented is easy enough, and it&apos;s inspired from delta-of-delta encoding. Instead of storing each timestamp for each data point, and since all the data points are aggregated on a regular interval, we transpose points to be the time difference divided by the interval. For example, the suite of timestamps &lt;code&gt;timestamps = [41230, 41235, 41240, 41250, 41255]&lt;/code&gt; is encoded into &lt;code&gt;timestamps = [41230, 1, 1, 2, 1], interval = 5&lt;/code&gt;. This allows regular compression algorithms to reduce the size of the integer list using &lt;a href=&quot;https://en.wikipedia.org/wiki/Run-length_encoding&quot;&gt;run-length encoding&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To actually compress the values, I tried two different algorithms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/LZ4_(compression_algorithm)&quot;&gt;LZ4&lt;/a&gt;, a fast compression/decompression algorithm&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The XOR based compression scheme described in the Gorilla paper mentioned above – that &lt;a href=&quot;https://gist.github.com/jd/b0aa5cbfa42f4eb23eb9&quot;&gt;I had to implement myself&lt;/a&gt;. For reference, it also exists a &lt;a href=&quot;http://golang.org&quot;&gt;Go&lt;/a&gt; implementation in &lt;a href=&quot;https://github.com/dgryski/go-tsz&quot;&gt;go-tsz&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I then benchmarked these solutions:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-carbonara-compression-speed.png&quot; alt=&quot;gnocchi-carbonara-compression-speed&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The XOR algorithm implemented in Python is pretty slow, compared to LZ4. Truth is that &lt;a href=&quot;https://github.com/steeve/python-lz4&quot;&gt;python-lz4&lt;/a&gt; is fully implemented in C, which makes it fast. I&apos;ve profiled my XOR implementation in Python, to discover that one operation took 20 % of the time: &lt;code&gt;count_lead_and_trail_zeroes&lt;/code&gt;, which is in charge of counting the number of leading and trailing zeroes in a binary number.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-carbonara-xor-profiling.png&quot; alt=&quot;gnocchi-carbonara-xor-profiling&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I tried 2 Python implementations of the same algorithm (and submitted them to my friend and Python developer &lt;a href=&quot;http://haypo-notes.readthedocs.org/&quot;&gt;Victor Stinner&lt;/a&gt; by the way).&lt;/p&gt;
&lt;p&gt;The first version using string search with &lt;code&gt;.index()&lt;/code&gt; is 10× faster than the second one that only do integer computation. Ah, Python… As Victor explained, each Python operation is slow and there&apos;s a lot in the second version, whereas &lt;code&gt;.index()&lt;/code&gt; is implemented in C and really well optimized and only needs 2 Python operations.&lt;/p&gt;
&lt;p&gt;Finally, I ended up optimizing that code by leveraging &lt;a href=&quot;https://cffi.readthedocs.org/en/latest/&quot;&gt;cffi&lt;/a&gt; to use directly &lt;code&gt;ffsll()&lt;/code&gt; and &lt;code&gt;flsll()&lt;/code&gt;. That decreased the run-time of &lt;code&gt;count_lead_and_trail_zeroes&lt;/code&gt; by 45 %, making the entire XOR compression code speed increased by a small 7 %. This is not enough to catch up with LZ4 speed. At this stage, the only solution to achieve a high-speed would probably to go with a full C implementation.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-carbonara-compression-size.png&quot; alt=&quot;gnocchi-carbonara-compression-size&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Considering the compression ratio of the different algorithms, they are pretty much identical. The worst case scenario (random values) for LZ4 compress down to 9 bytes per data point, whereas XOR can go down to 7.38 bytes per data point. In general XOR encoding beats LZ4 by 15 %, except for cases where all values are 0 or 1. However, LZ4 is faster than XOR by a factor of 4×-70× depending on cases.&lt;/p&gt;
&lt;p&gt;That means that we&apos;ll use LZ4 for data compression in Gnocchi 2.0. It&apos;s possible that we could achieve as fast compression/decompression algorithm, but I don&apos;t think it&apos;s worth the effort right now – it&apos;d represent a lot of code to write and to maintain.&lt;/p&gt;
</content:encoded></item><item><title>FOSDEM 2016, recap</title><link>https://julien.danjou.info/blog/fosdem-2016-recap/</link><guid isPermaLink="true">https://julien.danjou.info/blog/fosdem-2016-recap/</guid><description>Last week-end, I was in Brussels, Belgium for the FOSDEM, one of the greatest open source developer conference.</description><pubDate>Sat, 06 Feb 2016 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last week-end, I was in Brussels, Belgium for the &lt;a href=&quot;http://fosdem.org&quot;&gt;FOSDEM&lt;/a&gt;, one of the greatest open source developer conference. I was not sure to go there this year (I already skipped it in 2015), but it turned out I was requested to do a talk in the shared &lt;a href=&quot;https://fosdem.org/2016/schedule/track/lua/&quot;&gt;Lua&lt;/a&gt; &amp;amp; &lt;a href=&quot;https://fosdem.org/2016/schedule/track/gnu_guile/&quot;&gt;GNU Guile&lt;/a&gt; devroom.&lt;/p&gt;
&lt;p&gt;As a long time &lt;a href=&quot;http://lua.org&quot;&gt;Lua&lt;/a&gt; user and developer, and a follower of &lt;a href=&quot;http://www.gnu.org/software/guile/&quot;&gt;GNU Guile&lt;/a&gt; for several years, the organizer asked me to run a talk that would be a link between the two languages.&lt;/p&gt;
&lt;p&gt;I&apos;ve entitled my talk &quot;How awesome ended up with Lua and not Guile&quot; and gave it to a room full of interested users of the awesome window manager 🙂.&lt;/p&gt;
&lt;p&gt;We continued with a panel discussion entitled &quot;&lt;a href=&quot;https://fosdem.org/2016/schedule/event/future_guile_lua/&quot;&gt;The future of small languages Experience of Lua and Guile&lt;/a&gt;&quot; composed of Andy Wingo, Christopher Webber, Ludovic Courtès, Etiene Dalcol, Hisham Muhammaad and myself. It was a pretty interesting discussion, where both language shared their views on the state of their languages.&lt;/p&gt;
&lt;p&gt;It was a bit awkward to talk about Lua &amp;amp; Guile whereas most of my knowledge was years old, but it turns out many things didn&apos;t change. I hope I was able to provide interesting hindsight to both community. Finally, it was a pretty interesting FOSDEM to me, and it was a long time I didn&apos;t give talk here, so I really enjoyed it. See you next year!&lt;/p&gt;
</content:encoded></item><item><title>Profiling Python using cProfile: a concrete case</title><link>https://julien.danjou.info/blog/guide-to-python-profiling-cprofile-concrete-case-carbonara/</link><guid isPermaLink="true">https://julien.danjou.info/blog/guide-to-python-profiling-cprofile-concrete-case-carbonara/</guid><description>Writing programs is fun, but making them fast can be a pain. Python programs are no exception to that, but the basic profiling toolchain is actually not that complicated to use. Here, I would like to</description><pubDate>Mon, 16 Nov 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Writing programs is fun, but making them fast can be a pain. Python programs are no exception to that, but the basic profiling toolchain is actually not that complicated to use. Here, I would like to show you how you can quickly profile and analyze your Python code to find what part of the code you should optimize.&lt;/p&gt;
&lt;h2&gt;What&apos;s profiling?&lt;/h2&gt;
&lt;p&gt;Profiling a Python program is doing a dynamic analysis that measures the execution time of the program and everything that compose it. That means measuring the time spent in each of its functions. This will give you data about where your program is spending time, and what area might be worth optimizing.&lt;/p&gt;
&lt;p&gt;It&apos;s a very interesting exercise. Many people focus on local optimizations, such as determining e.g. which of the Python functions &lt;code&gt;range&lt;/code&gt; or &lt;code&gt;xrange&lt;/code&gt; is going to be faster. It turns out that knowing which one is faster may never be an issue in your program, and that the time gained by one of the functions above might not be worth the time you spend researching that, or arguing about it with your colleague.&lt;/p&gt;
&lt;p&gt;Trying to blindly optimize a program without measuring where it is actually spending its time is a useless exercise. Following your guts alone is not always sufficient.&lt;/p&gt;
&lt;p&gt;There are many types of profiling, as there are many things you can measure. In this exercise, we&apos;ll focus on CPU utilization profiling, meaning the time spent by each function executing instructions. Obviously, we could do many more kind of profiling and optimizations, such as memory profiling which would measure the memory used by each piece of code – something I talk about in &lt;a href=&quot;https://thehackerguidetopython.com&quot;&gt;The Hacker&apos;s Guide to Python&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;cProfile&lt;/h2&gt;
&lt;p&gt;Since Python 2.5, Python provides a C module called &lt;em&gt;&lt;a href=&quot;https://docs.python.org/2/library/profile.html&quot;&gt;cProfile&lt;/a&gt;&lt;/em&gt; which has a reasonable overhead and offers a good enough feature set. The basic usage goes down to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; import cProfile
&amp;gt;&amp;gt;&amp;gt; cProfile.run(&apos;2 + 2&apos;)
         2 function calls in 0.000 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.000    0.000 &amp;lt;string&amp;gt;:1(&amp;lt;module&amp;gt;)
        1    0.000    0.000    0.000    0.000 {method &apos;disable&apos; of &apos;_lsprof.Profiler&apos; objects}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Though you can also run a script with it, which turns out to be handy:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python -m cProfile -s cumtime lwn2pocket.py
         72270 function calls (70640 primitive calls) in 4.481 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.004    0.004    4.481    4.481 lwn2pocket.py:2(&amp;lt;module&amp;gt;)
        1    0.001    0.001    4.296    4.296 lwn2pocket.py:51(main)
        3    0.000    0.000    4.286    1.429 api.py:17(request)
        3    0.000    0.000    4.268    1.423 sessions.py:386(request)
      4/3    0.000    0.000    3.816    1.272 sessions.py:539(send)
        4    0.000    0.000    2.965    0.741 adapters.py:323(send)
        4    0.000    0.000    2.962    0.740 connectionpool.py:421(urlopen)
        4    0.000    0.000    2.961    0.740 connectionpool.py:317(_make_request)
        2    0.000    0.000    2.675    1.338 api.py:98(post)
       30    0.000    0.000    1.621    0.054 ssl.py:727(recv)
       30    0.000    0.000    1.621    0.054 ssl.py:610(read)
       30    1.621    0.054    1.621    0.054 {method &apos;read&apos; of &apos;_ssl._SSLSocket&apos; objects}
        1    0.000    0.000    1.611    1.611 api.py:58(get)
        4    0.000    0.000    1.572    0.393 httplib.py:1095(getresponse)
        4    0.000    0.000    1.572    0.393 httplib.py:446(begin)
       60    0.000    0.000    1.571    0.026 socket.py:410(readline)
        4    0.000    0.000    1.571    0.393 httplib.py:407(_read_status)
        1    0.000    0.000    1.462    1.462 pocket.py:44(wrapped)
        1    0.000    0.000    1.462    1.462 pocket.py:152(make_request)
        1    0.000    0.000    1.462    1.462 pocket.py:139(_make_request)
        1    0.000    0.000    1.459    1.459 pocket.py:134(_post_request)
[…]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This prints out all the function called, with the time spend in each and the number of times they have been called.&lt;/p&gt;
&lt;h3&gt;Advanced visualization with KCacheGrind&lt;/h3&gt;
&lt;p&gt;While being useful, the output format is very basic and does not make easy to grab knowledge for complete programs. For more advanced visualization, I leverage &lt;a href=&quot;https://kcachegrind.github.io/html/Home.html&quot;&gt;KCacheGrind&lt;/a&gt;. If you did any C programming and profiling these last years, you may have used it as it is primarily designed as front-end for &lt;a href=&quot;http://valgrind.org/&quot;&gt;Valgrind&lt;/a&gt; generated call-graphs.&lt;/p&gt;
&lt;p&gt;In order to use, you need to generate a &lt;em&gt;cProfile&lt;/em&gt; result file, then convert it to KCacheGrind format. To do that, I use &lt;em&gt;&lt;a href=&quot;https://pypi.python.org/pypi/pyprof2calltree&quot;&gt;pyprof2calltree&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python -m cProfile -o myscript.cprof myscript.py
$ pyprof2calltree -k -i myscript.cprof
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the KCacheGrind window magically appears!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/kcachegrind.png&quot; alt=&quot;kcachegrind&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Concrete case: Carbonara optimization&lt;/h2&gt;
&lt;p&gt;I was curious about the performances of &lt;a href=&quot;https://git.openstack.org/cgit/openstack/gnocchi/tree/gnocchi/carbonara.py&quot;&gt;Carbonara&lt;/a&gt;, the small timeseries library I wrote for &lt;a href=&quot;http://launchpad.net/gnocchi&quot;&gt;Gnocchi&lt;/a&gt;. I decided to do some basic profiling to see if there was any obvious optimization to do.&lt;/p&gt;
&lt;p&gt;In order to profile a program, you need to run it. But running the whole program in profiling mode can generate &lt;em&gt;a lot&lt;/em&gt; of data that you don&apos;t care about, and adds noise to what you&apos;re trying to understand. Since Gnocchi has thousands of unit tests and a few for Carbonara itself, I decided to profile the code used by these unit tests, as it&apos;s a good reflection of basic features of the library.&lt;/p&gt;
&lt;p&gt;Note that this is a good strategy for a curious and naive first-pass profiling.&lt;br /&gt;
There&apos;s no way that you can make sure that the hotspots you will see in the unit tests are the actual hotspots you will encounter in production. Therefore, a profiling in conditions and with a scenario that mimics what&apos;s seen in production is often a necessity if you need to push your program optimization further and want to achieve perceivable and valuable gain.&lt;/p&gt;
&lt;p&gt;I activated &lt;em&gt;cProfile&lt;/em&gt; using the method described above, creating a &lt;code&gt;cProfile.Profile&lt;/code&gt; object around my tests (I actually &lt;a href=&quot;https://github.com/testing-cabal/testtools/pull/163&quot;&gt;started to implement that in testtools&lt;/a&gt;). I then run &lt;em&gt;KCacheGrind&lt;/em&gt; as described above. Using &lt;em&gt;KCacheGrind&lt;/em&gt;, I generated the following figures.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/kcachegrind-carbonara-old-list.png&quot; alt=&quot;kcachegrind-carbonara-old-list&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The test I profiled here is called &lt;code&gt;test_fetch&lt;/code&gt; and is pretty easy to understand: it puts data in a timeserie object, and then fetch the aggregated result. The above list shows that 88 % of the ticks are spent in &lt;code&gt;set_values&lt;/code&gt; (44 ticks over 50). This function is used to insert values into the timeserie, not to fetch the values. That means that it&apos;s really slow to insert data, and pretty fast to actually retrieve them.&lt;/p&gt;
&lt;p&gt;Reading the rest of the list indicates that several functions share the rest of the ticks, &lt;code&gt;update&lt;/code&gt;, &lt;code&gt;_first_block_timestamp&lt;/code&gt;, &lt;code&gt;_truncate&lt;/code&gt;, &lt;code&gt;_resample&lt;/code&gt;, etc. Some of the functions in the list are not part of Carbonara, so there&apos;s no point in looking to optimize them. The only thing that can be optimized is, sometimes, the number of times they&apos;re called.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/kcachegrind-carbonara-old-graph.png&quot; alt=&quot;kcachegrind-carbonara-old-graph&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The call graph gives me a bit more insight about what&apos;s going on here. Using my knowledge about how Carbonara works, I don&apos;t think that the whole stack on the left for &lt;code&gt;_first_block_timestamp&lt;/code&gt; makes much sense. This function is supposed to find the first timestamp for an aggregate, e.g. with a timestamp of 13:34:45 and a period of 5 minutes, the function should return 13:30:00. The way it works currently is by calling the &lt;code&gt;resample&lt;/code&gt; function from Pandas on a timeserie with only one element, but that seems to be very slow. Indeed, currently this function represents 25 % of the time spent by &lt;code&gt;set_values&lt;/code&gt; (11 ticks on 44).&lt;/p&gt;
&lt;p&gt;Fortunately, I recently added a small function called &lt;code&gt;_round_timestamp&lt;/code&gt; that does exactly what &lt;code&gt;_first_block_timestamp&lt;/code&gt; needs that without calling any Pandas function, so no &lt;code&gt;resample&lt;/code&gt;. So I ended up rewriting that function this way:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;     def _first_block_timestamp(self):
-        ts = self.ts[-1:].resample(self.block_size)
-        return (ts.index[-1] - (self.block_size * self.back_window))
+        rounded = self._round_timestamp(self.ts.index[-1], self.block_size)
+        return rounded - (self.block_size * self.back_window)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then I re-run the exact same test to compare the output of &lt;em&gt;cProfile&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/kcachegrind-carbonara-new-list.png&quot; alt=&quot;kcachegrind-carbonara-new-list&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The list of function seems quite different this time. The number of time spend used by &lt;code&gt;set_values&lt;/code&gt; dropped from 88 % to 71 %.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/kcachegrind-carbonara-new-graph.png&quot; alt=&quot;kcachegrind-carbonara-new-graph&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The call stack for &lt;code&gt;set_values&lt;/code&gt; shows that pretty well: we can&apos;t even see the &lt;code&gt;_first_block_timestamp&lt;/code&gt; function as it is so fast that it totally disappeared from the display. It&apos;s now being considered insignificant by the profiler.&lt;/p&gt;
&lt;p&gt;So we just speed up the whole insertion process of values into Carbonara by a nice 25 % in a few minutes. Not that bad for a first naive pass, right?&lt;/p&gt;
&lt;p&gt;If you want to know more, I wrote a whole chapter about optimizing code in &lt;a href=&quot;https://scaling-python.com&quot;&gt;Scaling Python&lt;/a&gt;. Check it out!&lt;/p&gt;
</content:encoded></item><item><title>Gnocchi 1.3.0 release</title><link>https://julien.danjou.info/blog/gnocchi-1-3-0-released/</link><guid isPermaLink="true">https://julien.danjou.info/blog/gnocchi-1-3-0-released/</guid><description>Finally, Gnocchi 1.3.0 is out. This is our final release, more or less matching the OpenStack 6 months schedule, that concludes the Liberty development cycle.</description><pubDate>Wed, 04 Nov 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Finally, &lt;a href=&quot;https://launchpad.net/gnocchi/trunk/1.3.0&quot;&gt;Gnocchi 1.3.0&lt;/a&gt; is out. This is our final release, more or less matching the OpenStack 6 months schedule, that concludes the Liberty development cycle.&lt;/p&gt;
&lt;p&gt;This release was supposed to be released a few weeks earlier, but our integration test got completely blocked for several days just the week before the OpenStack Mitaka summit.&lt;/p&gt;
&lt;h2&gt;New website&lt;/h2&gt;
&lt;p&gt;We build a new dedicated website for Gnocchi at &lt;a href=&quot;http://gnocchi.xyz&quot;&gt;gnocchi.xyz&lt;/a&gt;. We want to promote Gnocchi outside of the &lt;a href=&quot;http://openstack.org&quot;&gt;OpenStack&lt;/a&gt; bubble, as it a useful timeseries database on its own that can work without the rest of the stack. We&apos;ll try to improve the documentation. If you&apos;re curious, feel free to check it out and report anything you miss!&lt;/p&gt;
&lt;h2&gt;The speed bump&lt;/h2&gt;
&lt;p&gt;Obviously, if it was a bug in Gnocchi that we have hit, it would have been quick to fix. However, we found &lt;a href=&quot;https://bugs.launchpad.net/python-keystoneclient/+bug/1508424&quot;&gt;a nasty bug&lt;/a&gt; in Swift caused by the evil monkey-patching of Eventlet (once again) blended with a mixed usage of native threads and Eventlet threads in Swift. Shake all of that, and you got yourself pretty race conditions when using the Keystone middleware authentication.&lt;/p&gt;
&lt;p&gt;In the meantime, we disabled Swift multi-threading by using mod_wsgi instead of Eventlet in devstack.&lt;/p&gt;
&lt;h2&gt;New features&lt;/h2&gt;
&lt;p&gt;So what&apos;s new in this new shiny release? A few interesting things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Metric deletion is now asynchronous. That&apos;s not the most used feature in the REST API – weirdly people do not often delete metrics – but it&apos;s now way faster and reliable by being asynchronous. &lt;em&gt;Metricd&lt;/em&gt; is now in charge of cleaning up things up.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Speed improvement. We are now confident to be even more faster than in the &lt;a href=&quot;https://julien.danjou.info/blog/gnocchi-benchmarks&quot;&gt;latest benchmarks I run&lt;/a&gt; (around 1.5-2× faster), which makes Gnocchi &lt;em&gt;really&lt;/em&gt; fast with its native storage back-ends. We profiled and optimized Carbonara and the REST API data validation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Improve &lt;em&gt;metricd&lt;/em&gt; status report. It now reports the size of the backlog of the whole cluster both in its log and via the REST API. Easy monitoring!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ceph drivers enhancement. We had people testing the Ceph drivers in production, so we made a few changes and fixes to it to make it more solid.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And that&apos;s all we did in the last couple of months. We have a lot of things on the roadmap that are pretty exciting, and I&apos;ll sure talk about them in the next weeks.&lt;/p&gt;
</content:encoded></item><item><title>OpenStack Summit Mitaka from a Telemetry point of view</title><link>https://julien.danjou.info/blog/openstack-summit-mitaka-tokyo-telemetry/</link><guid isPermaLink="true">https://julien.danjou.info/blog/openstack-summit-mitaka-tokyo-telemetry/</guid><description>Last week I was in Tokyo, Japan for the OpenStack Summit, discussing the new Mitaka version that will be released in 6 months.</description><pubDate>Mon, 02 Nov 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last week I was in Tokyo, Japan for the &lt;a href=&quot;https://www.openstack.org/summit/tokyo-2015/&quot;&gt;OpenStack Summit&lt;/a&gt;, discussing the new Mitaka version that will be released in 6 months.&lt;/p&gt;
&lt;p&gt;I&apos;ve attended the summit mainly to discuss and follow-up new developments on &lt;a href=&quot;http://launchpad.net/ceilometer&quot;&gt;Ceilometer&lt;/a&gt;, &lt;a href=&quot;http://launchpad.net/gnocchi&quot;&gt;Gnocchi&lt;/a&gt;, &lt;a href=&quot;http://launchpad.net/aodh&quot;&gt;Aodh&lt;/a&gt; and Oslo. It has been a pretty good week and we were able to discuss and plan a few interesting things. Below are what I found remarkable during this summit concerning those projects.&lt;/p&gt;
&lt;h2&gt;Distributed lock manager&lt;/h2&gt;
&lt;p&gt;I did not attend this session, but I need to write something about it.&lt;/p&gt;
&lt;p&gt;See, when working in a distributed environment like OpenStack, it&apos;s almost obvious that sooner or later you end up needing a distributed lock mechanism. It started to be pretty obvious and a serious problem for us 2 years ago in Ceilometer. Back then, we proposed the &lt;a href=&quot;https://wiki.openstack.org/wiki/Oslo/blueprints/service-sync&quot;&gt;service-sync&lt;/a&gt; blueprint and talked about it during the OpenStack Icehouse Design Summit in Hong-Kong. The session at that time was a success, and in 20 minutes I convinced everyone it was the good thing to do. The night following the session, we picked a named, Tooz, to name this new library. It was the first time I met Joshua Harlow, which became one of the biggest Tooz contributor since then.&lt;/p&gt;
&lt;p&gt;For the following months, we tried to move the lines in OpenStack. It was very hard to convince people that it was the solution to their problem. Most of the time, they did not seem to grasp the entirety of what was at stake.&lt;/p&gt;
&lt;p&gt;This time, it seems that we managed to convince everyone that a DLM is indeed needed. Joshua wrote an extensive specification called &lt;a href=&quot;https://review.openstack.org/#/c/209661/&quot;&gt;Chronicle of a DLM&lt;/a&gt;, which ended up being discussed and somehow adopted during that session in Tokyo.&lt;/p&gt;
&lt;p&gt;So yes, Tooz will be the weapon of choice for OpenStack. It will avoid a hard requirement on any DLM solution directly. The best driver right now is the &lt;a href=&quot;https://zookeeper.apache.org/&quot;&gt;ZooKeeper&lt;/a&gt; one, but it&apos;ll still be possible for operators to use e.g. Redis.&lt;/p&gt;
&lt;p&gt;This is a great achievement for us, after spending years trying to fix features such as the &lt;a href=&quot;https://blueprints.launchpad.net/nova/+spec/tooz-for-service-groups&quot;&gt;Nova service group subsystem&lt;/a&gt; and seeing our proposals postponed forever.&lt;/p&gt;
&lt;p&gt;(If you want to know more, &lt;a href=&quot;http://lwn.net&quot;&gt;LWN.net&lt;/a&gt; has&lt;br /&gt;
&lt;a href=&quot;https://lwn.net/Articles/662140/&quot;&gt;a great article about that session&lt;/a&gt;.)&lt;/p&gt;
&lt;h2&gt;Telemetry team name&lt;/h2&gt;
&lt;p&gt;With the new projects launched this last year, Aodh &amp;amp; Gnocchi, in parallel of the old Ceilometer, plus the change from programs to Big Tent in OpenSack, the team is having an identity issue. Being referred to as the &quot;Ceilometer team&quot; is not really accurate, as some of us only work on Aodh or on Gnocchi. So after discussing that, I &lt;a href=&quot;https://review.openstack.org/#/c/240809/&quot;&gt;proposed to rename the team to Telemetry&lt;/a&gt; instead. We&apos;ll see how it goes.&lt;/p&gt;
&lt;h2&gt;Alarms&lt;/h2&gt;
&lt;p&gt;The first session was about alarms and the Aodh project. It turns out that the project is in pretty good shape, but probably need some more love, which I hope I&apos;ll be able to provide in the next months.&lt;/p&gt;
&lt;p&gt;The need for a new &lt;em&gt;aodhclient&lt;/em&gt; based on the technologies we recently used building &lt;em&gt;gnocchiclient&lt;/em&gt; has been reasserted, so we might end up working on that pretty soon. The Tempest support also needs some improvement, and we have a plan to enhance that.&lt;/p&gt;
&lt;h2&gt;Data visualisation&lt;/h2&gt;
&lt;p&gt;We got David Lyle in this session, the Project Technical Leader for &lt;a href=&quot;http://openstack/horizon&quot;&gt;Horizon&lt;/a&gt;. It was an interesting discussion. It used to be technically challenging to draw charts from the data Ceilometer collects, but it&apos;s now very easy with Gnocchi and its API.&lt;/p&gt;
&lt;p&gt;While the technical side is resolved, the more political and user experience side of was to draw and how was discussed at length. We don&apos;t want to make people think that Ceilometer and Gnocchi are a full monitoring solution, so there&apos;s some precaution to take. Other than that, it would be pretty cool to have view of the data in Horizon.&lt;/p&gt;
&lt;h2&gt;Rolling upgrade&lt;/h2&gt;
&lt;p&gt;It turns out that Ceilometer has an architecture that makes it easy to have rolling upgrade. We just need to write a proper documentation explaining how to do it and in which order the services should be upgraded.&lt;/p&gt;
&lt;h2&gt;Ceilometer splitting&lt;/h2&gt;
&lt;p&gt;The split of the alarm feature of Ceilometer in its own project Aodh in the last cycle was a great success for the whole team. We want to split other pieces of Ceilometer, as they make sense on their own, makes it easier to manage. They are also some projects that want to use them without the whole stack, so that&apos;s a good idea to make it happen.&lt;/p&gt;
&lt;h2&gt;CloudKitty &amp;amp; Gnocchi&lt;/h2&gt;
&lt;p&gt;I attended the 2 sessions that were allocated to &lt;a href=&quot;https://wiki.openstack.org/wiki/CloudKitty&quot;&gt;CloudKitty&lt;/a&gt;. It was pretty interesting as they want to simplify their architecture and leverage what Gnocchi provides. I proposed my view of the project architecture and how they could leverage the more of Gnocchi to retrieve and store data. They want to go in that direction though it&apos;s a large amount of work and refactoring on their side, so it&apos;ll take time.&lt;/p&gt;
&lt;p&gt;We also need to enhance the support of extension for new resources in Gnocchi, and that&apos;s something I hope I&apos;ll work on in the next months.&lt;/p&gt;
&lt;p&gt;Overall, this summit was pretty good and I got a tremendous amount of good feedback on Gnocchi. I again managed to get enough ideas and tasks to tackle for the next 6 months. It really looks interesting to see where the whole team will go from that. Stay tuned!&lt;/p&gt;
</content:encoded></item><item><title>Benchmarking Gnocchi for fun &amp; profit</title><link>https://julien.danjou.info/blog/gnocchi-benchmarks/</link><guid isPermaLink="true">https://julien.danjou.info/blog/gnocchi-benchmarks/</guid><description>We got pretty good feedback on Gnocchi so far, even if we only had little. Recently, in order to have a better feeling of where we were at, we wanted to know how fast (or slow) Gnocchi was.</description><pubDate>Tue, 13 Oct 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We got pretty good feedback on &lt;a href=&quot;http://launchpad.net/gnocchi&quot;&gt;Gnocchi&lt;/a&gt; so far, even if we only had little. Recently, in order to have a better feeling of where we were at, we wanted to know how fast (or slow) Gnocchi was.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://julien.danjou.info/openstack-ceilometer-the-gnocchi-experiment.html&quot;&gt;early benchmarks that some of the Mirantis engineers ran last year&lt;/a&gt; showed pretty good signs. But a year later, it was time to get real numbers and have a good understanding of Gnocchi capacity.&lt;/p&gt;
&lt;h2&gt;Benchmark tools&lt;/h2&gt;
&lt;p&gt;The first thing I realized when starting that process, is that we were lacking of tools to run benchmarks. Therefore I started to write some benchmark tools in &lt;a href=&quot;https://launchpad.net/python-gnocchiclient&quot;&gt;python-gnocchiclient&lt;/a&gt;, which provides a command line tool to interrogate Gnocchi. I added a few basic commands to measure metric performance, such as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ gnocchi benchmark metric create -w 48 -n 10000 -a low
+----------------------+------------------+
| Field                | Value            |
+----------------------+------------------+
| client workers       | 48               |
| create executed      | 10000            |
| create failures      | 0                |
| create failures rate | 0.00 %           |
| create runtime       | 8.80 seconds     |
| create speed         | 1136.96 create/s |
| delete executed      | 10000            |
| delete failures      | 0                |
| delete failures rate | 0.00 %           |
| delete runtime       | 39.56 seconds    |
| delete speed         | 252.75 delete/s  |
+----------------------+------------------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The command line tool supports the &lt;code&gt;--verbose&lt;/code&gt; switch to have detailed progress report on the benchmark progression. So far it supports metric operations only, but that&apos;s the most interesting part of Gnocchi.&lt;/p&gt;
&lt;h2&gt;Spinning up some hardware&lt;/h2&gt;
&lt;p&gt;I got a couple of bare metal servers to test Gnocchi on. I dedicated the first one to Gnocchi, and used the second one as the benchmark client, plugged on the same network. Each server is made of&lt;br /&gt;
2×&lt;a href=&quot;http://ark.intel.com/products/81897/Intel-Xeon-Processor-E5-2609-v3-15M-Cache-1_90-GHz&quot;&gt;Intel Xeon E5-2609 v3&lt;/a&gt; (12 cores in total) and 32 GB of RAM. That provides a lot of CPU to handle requests in parallel.&lt;/p&gt;
&lt;p&gt;Then I simply performed a basic &lt;a href=&quot;http://www.redhat.com/en/technologies/linux-platforms/enterprise-linux&quot;&gt;RHEL 7&lt;/a&gt; installation and ran &lt;a href=&quot;http://devstack.org&quot;&gt;devstack&lt;/a&gt; to spin up an installation of Gnocchi based on the master branch, disabling all of the others OpenStack components. I then tweaked the Apache httpd configuration to use the worker MPM and increased the maximum number of clients that can sent request simultaneously.&lt;/p&gt;
&lt;p&gt;I configured Gnocchi to use the &lt;em&gt;PostsgreSQL&lt;/em&gt; indexer, as it&apos;s the recommended one, and the &lt;em&gt;file&lt;/em&gt; storage driver, based on Carbonara (Gnocchi own storage engine). That means files were stored locally rather than in Ceph or Swift.&lt;/p&gt;
&lt;p&gt;Using the &lt;em&gt;file&lt;/em&gt; driver is less scalable (you have to run on only one node or uses a technology like NFS to share the files), but it was good enough for this benchmark and to have some numbers and profiling the beast.&lt;/p&gt;
&lt;p&gt;The OpenStack Keystone authentication middleware was not enabled in this setup, as it would add some delay validating the authentication token.&lt;/p&gt;
&lt;h2&gt;Metric CRUD operations&lt;/h2&gt;
&lt;p&gt;Metric creation is pretty fast. I managed to attain 1300 metric/s created pretty easily. Deletion is now asynchronous, which means it&apos;s faster than in Gnocchi 1.2, but it&apos;s still slower than creation: 500 metric/s can be deleted. That does not sound like a huge issue since metric deletion is actually barely used in production.&lt;/p&gt;
&lt;p&gt;Retrieving metric information is also pretty fast and goes up to 800 metric/s. It&apos;d be easy to achieve very higher throughput for this one, as it&apos;d be easy to cache, but we didn&apos;t feel the need to implement it so far.&lt;/p&gt;
&lt;p&gt;Another important thing is that all of these numbers are constant and barely depends on the number of the metric already managed by Gnocchi.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;th&gt;Rate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Create metric&lt;/td&gt;
&lt;td&gt;Created 100k metrics in 77 seconds&lt;/td&gt;
&lt;td&gt;1300 metric/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Show metric&lt;/td&gt;
&lt;td&gt;Show a metric 100k times in 149 seconds&lt;/td&gt;
&lt;td&gt;670 metric/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delete metric&lt;/td&gt;
&lt;td&gt;Deleted 100k metrics in 190 seconds&lt;/td&gt;
&lt;td&gt;524 metric/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Sending and getting measures&lt;/h2&gt;
&lt;p&gt;Pushing measures into metrics is one of the hottest topic. Starting with Gnocchi 1.1, the measures pushed are treated asynchronously, which makes it much faster to push new measures. Getting new numbers on that feature was pretty interesting.&lt;/p&gt;
&lt;p&gt;The number of metric per second you can push depends on the batch size, meaning the number of actual measurements you send per call. The naive approach is to push 1 measure per call, and in that case, Gnocchi is able to handle around 600 measures/s. With a batch containing 100 measures, the number of calls per second goes down to 450, but since you push 100 measures each time, that means 45k measures per second pushed into Gnocchi!&lt;/p&gt;
&lt;p&gt;I&apos;ve pushed the test further, inspired by the recent &lt;a href=&quot;https://influxdb.com/blog/2015/10/07/the_new_influxdb_storage_engine_a_time_structured_merge_tree.html&quot;&gt;blog post of InfluxDB claiming to achieve 300k points per second&lt;/a&gt; with their new engine. I ran the same benchmark on the hardware I had, which is roughly two times smaller than the one they used. I achieved to push Gnocchi to a little more than 120k measurement per second. If I had same hardware as they used, I could interpolate the results to achieve almost 250k measures/s pushed. Obviously, you can&apos;t strictly compare Gnocchi and InfluxDB since they are not doing exactly the same thing, but it still looks way better than what I expected.&lt;/p&gt;
&lt;p&gt;Using smaller batch sizes of 1k or 2k improve the throughput further to around 125k measures/s.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;th&gt;Rate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Push metric 5k&lt;/td&gt;
&lt;td&gt;Push 5M measures with batch of 5k measures in 40 seconds&lt;/td&gt;
&lt;td&gt;122k measures/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push metric 4k&lt;/td&gt;
&lt;td&gt;Push 5M measures with batch of 4k measures in 40 seconds&lt;/td&gt;
&lt;td&gt;125k measures/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push metric 3k&lt;/td&gt;
&lt;td&gt;Push 5M measures with batch of 3k measures in 40 seconds&lt;/td&gt;
&lt;td&gt;123k measures/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push metric 2k&lt;/td&gt;
&lt;td&gt;Push 5M measures with batch of 2k measures in 41 seconds&lt;/td&gt;
&lt;td&gt;121k measures/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push metric 1k&lt;/td&gt;
&lt;td&gt;Push 5M measures with batch of 1k measures in 44 seconds&lt;/td&gt;
&lt;td&gt;113k measures/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push metric 500&lt;/td&gt;
&lt;td&gt;Push 5M measures with batch of 500 measures in 51 seconds&lt;/td&gt;
&lt;td&gt;98k measures/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push metric 100&lt;/td&gt;
&lt;td&gt;Push 5M measures with batch of 100 measures in 112 seconds&lt;/td&gt;
&lt;td&gt;45k measures/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push metric 10&lt;/td&gt;
&lt;td&gt;Push 5M measures with batch of 10 measures in 852 seconds&lt;/td&gt;
&lt;td&gt;6k measures/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push metric 1&lt;/td&gt;
&lt;td&gt;Push 500k measures with batch of 1 measure in 800 seconds&lt;/td&gt;
&lt;td&gt;624 measures/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Get measures&lt;/td&gt;
&lt;td&gt;Push 43k measures of 1 metric&lt;/td&gt;
&lt;td&gt;260k measures/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;What about getting measures? Well, it&apos;s actually pretty fast too. Retrieving a metric with 1 month of data with 1 minute interval (that&apos;s 43k points) takes less than 2 second.&lt;/p&gt;
&lt;p&gt;Though it&apos;s actually slower than what I expected. The reason seems to be that the JSON is 2 MB big and encoding it takes a lot of time for Python. I&apos;ll investigate that. Another point I discovered, is that by default Gnocchi returns all the datapoints for each granularities available for the asked period, which might double the size of the returned data for nothing if you don&apos;t need it. It&apos;ll be easy to add an option to the API to only retrieve what you need though!&lt;/p&gt;
&lt;p&gt;Once benchmarked, that meant I was able to retrieve 6 metric/s per second, which translates to around 260k measures/s.&lt;/p&gt;
&lt;h2&gt;&lt;em&gt;Metricd&lt;/em&gt; speed&lt;/h2&gt;
&lt;p&gt;New measures that are pushed into Gnocchi are processed asynchronously by the &lt;code&gt;gnocchi-metricd&lt;/code&gt; daemon. When doing the benchmarks above, I ran into a very interesting issue: sending 10k measures on a metric would make &lt;code&gt;gnocchi-metricd&lt;/code&gt; uses up to 2 GB RAM and 120 % CPU for more than 10 minutes.&lt;/p&gt;
&lt;p&gt;After further investigation, I found that the naive approach we used to resample datapoints in Carbonara using &lt;a href=&quot;http://pandas.pydata.org/&quot;&gt;Pandas&lt;/a&gt; was causing that. I &lt;a href=&quot;https://github.com/pydata/pandas/issues/11217&quot;&gt;reported a bug on Pandas&lt;/a&gt; and the upstream author was kind enough to provide a nice workaround, that I sent as &lt;a href=&quot;https://github.com/pydata/pandas/pull/11242&quot;&gt;a pull request&lt;/a&gt; to Pandas documentation.&lt;/p&gt;
&lt;p&gt;I wrote a fix for Gnocchi based on that, and started using it. Computing the standard aggregation methods set (std, count, 95pct, min, max, sum, median, mean) for 10k batches of 1 measure (worst case scenario) for one metric with 10k measures now takes only 20 seconds and uses 100 MB of RAM – 45× faster. That means that in normal operations, where only a few new measures are processed, the operation of updating a metric only takes a few milliseconds. Awesome!&lt;/p&gt;
&lt;h2&gt;Comparison with Ceilometer&lt;/h2&gt;
&lt;p&gt;For comparison sake, I&apos;ve quickly run some read operations benchmark in Ceilometer. I&apos;ve fed it with one month of samples for 100 instances polled every minute. That represents roughly 4.3M samples injected, and that took a while – almost 1 hour whereas it would have taken less than a minute in Gnocchi. Then I tried to retrieve some statistics in the same way that we provide them in Gnocchi, which mean aggregating them over a period of 60 seconds over a month.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;th&gt;Rate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Read metric SQL&lt;/td&gt;
&lt;td&gt;Read measures for 1 metric&lt;/td&gt;
&lt;td&gt;2min 58s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read metric MongoDB&lt;/td&gt;
&lt;td&gt;Read measures for 1 metric&lt;/td&gt;
&lt;td&gt;28s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read metric Gnocchi&lt;/td&gt;
&lt;td&gt;Read measures for 1 metric&lt;/td&gt;
&lt;td&gt;2s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Obviously, Ceilometer is very slow. It has to look into 4M of samples to compute and return the result, which takes a lot of time. Whereas Gnocchi just has to fetch a file and pass it over. That also means that the more samples you have (so the more time you collect data and the more resources you have), slower Ceilometer will become. This is not a problem with Gnocchi, as I emphasized when I started designing it.&lt;/p&gt;
&lt;p&gt;Most Gnocchi operations are &lt;em&gt;O(log R)&lt;/em&gt; where R is the number of metrics or resources, whereas most Ceilometer operations are &lt;em&gt;O(log S)&lt;/em&gt; where S is the number of samples (measures). Since is R millions of time smaller than S, Gnocchi gets to be much faster.&lt;/p&gt;
&lt;p&gt;And what&apos;s even more interesting, is that Gnocchi is entirely scalable horizontally. Adding more Gnocchi servers (for the API and its background processing worker &lt;em&gt;metricd&lt;/em&gt;) will multiply Gnocchi performances by the number of servers added.&lt;/p&gt;
&lt;h2&gt;Improvements&lt;/h2&gt;
&lt;p&gt;There are several things to improve in Gnocchi, such as splitting Carbonara archives to make them more efficient, especially from drivers such as Ceph and Swift. It&apos;s already on my plate, and I&apos;m looking forwarding to working on that!&lt;/p&gt;
&lt;p&gt;And if you have any questions, feel free to shoot them in the comment section. 😉&lt;/p&gt;
</content:encoded></item><item><title>Gnocchi talk at OpenStack Paris Meetup #16</title><link>https://julien.danjou.info/blog/openstack-france-paris-meetup-gnocchi-talk/</link><guid isPermaLink="true">https://julien.danjou.info/blog/openstack-france-paris-meetup-gnocchi-talk/</guid><description>Last week, I&apos;ve been invited to the OpenStack Paris meetup #16, whose subject was about metrics in OpenStack. Last time I spoke at this meetup was back in 2012, during the OpenStack Paris meetup #2.</description><pubDate>Mon, 05 Oct 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last week, I&apos;ve been invited to the &lt;a href=&quot;http://www.meetup.com/OpenStack-France/events/225227112/&quot;&gt;OpenStack Paris meetup #16&lt;/a&gt;, whose subject was about metrics in OpenStack. Last time I spoke at this meetup was back in 2012, during the &lt;a href=&quot;https://julien.danjou.info/blog/openstack-france-meetup-2&quot;&gt;OpenStack Paris meetup #2&lt;/a&gt;. A very long time ago!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-talk-2.jpg&quot; alt=&quot;gnocchi-talk-2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I talked for half an hour about &lt;a href=&quot;http://launchpad.net/gnocchi&quot;&gt;Gnocchi&lt;/a&gt;, the OpenStack project I&apos;ve been running for 18 months now. I started by explaining the story behind the project and why we needed to build it. Ceilometer has an interesting history and had a curious roadmap these last year, and I summarized that briefly. Then I talk about how Gnocchi works and what it offers to users and operators. The slides where full of JSON, but I imagine it offered a interesting view of what the API looks like and how easy it is to operate. This also allowed me to emphasize how many use cases are actually really covered and solved, contrary to what Ceilometer did so far. The talk has been well received and I got a few interesting questions at the end.&lt;/p&gt;
</content:encoded></item><item><title>My interview in le Journal du Hacker</title><link>https://julien.danjou.info/blog/interview-journal-du-hacker/</link><guid isPermaLink="true">https://julien.danjou.info/blog/interview-journal-du-hacker/</guid><description>Le Journal du Hacker interviewed me about my work on OpenStack, my job at Red Hat, and my self-published book The Hacker&apos;s Guide to Python.</description><pubDate>Thu, 17 Sep 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few days ago, the French equivalent of &lt;a href=&quot;https://news.ycombinator.com/&quot;&gt;Hacker News&lt;/a&gt;, called &quot;&lt;a href=&quot;https://www.journalduhacker.net/&quot;&gt;Le Journal du Hacker&lt;/a&gt;&quot;, &lt;a href=&quot;https://www.journalduhacker.net/s/l5qktw/journal_du_hacker_entretien_avec_julien_danjou_d_veloppeur_openstack&quot;&gt;interviewed me&lt;/a&gt; about my work on &lt;a href=&quot;http://openstack.org&quot;&gt;OpenStack&lt;/a&gt;, my job at &lt;a href=&quot;http://redhat.com&quot;&gt;Red Hat&lt;/a&gt; and my self-published book &lt;a href=&quot;https://thehackerguidetopython.com&quot;&gt;The Hacker&apos;s Guide to Python&lt;/a&gt;. I&apos;ve spent some time translating it into English so you can read it if you don&apos;t understand French! I hope you&apos;ll enjoy it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hi Julien, and thanks for participating in this interview for the Journal du Hacker. For our readers who don&apos;t know you, can you introduce you briefly?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You&apos;re welcome! My name is Julien, I&apos;m 31 years old, and I live in Paris. I now have been developing free software for around fifteen years. I had the pleasure to work (among other things) on &lt;a href=&quot;http://debian.org&quot;&gt;Debian&lt;/a&gt;, &lt;a href=&quot;https://www.gnu.org/software/emacs/&quot;&gt;Emacs&lt;/a&gt; and &lt;a href=&quot;http://awesome.naquadah.org&quot;&gt;awesome&lt;/a&gt; these last years, and more recently on OpenStack. Since a few months now, I work at Red Hat, as a Principal Software Engineer on &lt;a href=&quot;http://opensack.org&quot;&gt;OpenStack&lt;/a&gt;. I am in charge of doing upstream development for that cloud-computing platform, mainly around the Ceilometer, Aodh and Gnocchi projects.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Being myself a system architect, I follow your work in &lt;a href=&quot;http://openstack.org&quot;&gt;OpenStack&lt;/a&gt; since a while. It&apos;s uncommon to have the point of view of someone as implied as you are. Can you give us a summary of the state of the project, and then detail your activities in this project?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The &lt;a href=&quot;http://openstack.org&quot;&gt;OpenStack&lt;/a&gt; project has grown and changed a lot since I started 4 years ago. It started as a few projects providing the basics, like &lt;a href=&quot;https://launchpad.net/nova&quot;&gt;Nova&lt;/a&gt; (compute), &lt;a href=&quot;https://launchpad.net/swift&quot;&gt;Swift&lt;/a&gt; (object storage), &lt;a href=&quot;https://launchpad.net/cinder&quot;&gt;Cinder&lt;/a&gt; (volume), &lt;a href=&quot;https://launchpad.net/keystone&quot;&gt;Keystone&lt;/a&gt; (identity) or even &lt;a href=&quot;https://launchpad.net/neutron&quot;&gt;Neutron&lt;/a&gt; (network) who are basis for a cloud-computing platform, and finally became composed of a lot more projects.&lt;/p&gt;
&lt;p&gt;For a while, the inclusion of projects was the subject of a strict review from the technical committee. But since a few months, the rules have been relaxed, and we see a lot more projects connected to cloud-computing &lt;a href=&quot;http://governance.openstack.org/reference/projects/&quot;&gt;joining us&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As far as I&apos;m concerned, I&apos;ve started with a few others people the &lt;a href=&quot;http://governance.openstack.org/reference/projects/ceilometer.html&quot;&gt;Ceilometer&lt;/a&gt; project in 2012, devoted to handling metrics of OpenStack platforms. Our goal is to be able to collect all the metrics and record them to analyze them later. We also have a module providing the ability to trigger actions on threshold crossing (alarm).&lt;/p&gt;
&lt;p&gt;The project grew in a monolithic way, and in a linear way for the number of contributors, during the first two years. I was the PTL (Project Technical Leader) for a year. This leader position asks for a lot of time for bureaucratic things and people management, so I decided to leave my spot in order to be able to spend more time solving the technical challenges that Ceilometer offered.&lt;/p&gt;
&lt;p&gt;I&apos;ve started the &lt;a href=&quot;https://launchpad.net/gnocchi&quot;&gt;Gnocchi&lt;/a&gt; project in 2014. The first stable version (1.0.0) was released a few months ago. It&apos;s a timeseries database offering a REST API and a strong ability to scale. It was a necessary development to solve the problems tied to the large amount of metrics created by a cloud-computing platform, where tens of thousands of virtual machines have to be metered as often as possible. This project works as a standalone deployment or with the rest of OpenStack.&lt;/p&gt;
&lt;p&gt;More recently, I&apos;ve started &lt;a href=&quot;https://launchpad.net/aodh&quot;&gt;Aodh&lt;/a&gt;, the result of moving out the code and features of Ceilometer related to threshold action triggering (alarming). That&apos;s the logical suite to what we started with Gnocchi. It means Ceilometer is to be split into independent modules that can work together – with or without OpenStack. It seems to me that the features provided by Ceilometer, Aodh and Gnocchi can also be interesting for operators running more classical infrastructures. That&apos;s why I&apos;ve pushed the projects into that direction, and also to have a more service-oriented architecture (&lt;a href=&quot;https://fr.wikipedia.org/wiki/Architecture_orient%C3%A9e_services&quot;&gt;SOA&lt;/a&gt;).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I&apos;d like to stop for a moment on Ceilometer. I think that this solution was very expected, especially by the cloud-computing providers using OpenStack for billing resources sold to their customers. I remember reading a blog post where you were talking about the high-speed construction of this brick, and features that were not supposed to be there. Nowadays, with Gnocchi and Aodh, what is the quality of the brick Ceilometer and the programs it relies on?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Indeed, one of the first use-case for Ceilometer was tied to the ability to get metrics to feed a billing tool. That&apos;s now a reached goal since we have billing tools for OpenStack using Ceilometer, such as &lt;a href=&quot;https://wiki.openstack.org/wiki/CloudKitty&quot;&gt;CloudKitty&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;However, other use-cases appeared rapidly, such as the ability to trigger alarms. This feature was necessary, for example, to implement the auto scaling feature that &lt;a href=&quot;http://launchpad.net/heat&quot;&gt;Heat&lt;/a&gt; needed. At the time, for technical and political reasons, it was not possible to implement this feature in a new project, and the functionality ended up in Ceilometer, since it was using the metrics collected and stored by Ceilometer itself.&lt;/p&gt;
&lt;p&gt;Though, like I said, this feature is now in its own project, Aodh. The alarm feature is used since a few cycles in production, and the Aodh project brings new features on the table. It allows to trigger threshold actions and is one of the few solutions able to work at high scale with several thousands of alarms.&lt;br /&gt;
It&apos;s impossible to make Nagios run with millions of instances to fetch metrics and triggers alarms. Ceilometer and Aodh can do that easily on a few tens of nodes automatically.&lt;/p&gt;
&lt;p&gt;On the other side, Ceilometer has been for a long time painted as slow and complicated to use, because its metrics storage system was by default using &lt;a href=&quot;https://www.mongodb.org/&quot;&gt;MongoDB&lt;/a&gt;. Clearly, the data structure model picked was not optimal for what the users were doing with the data.&lt;/p&gt;
&lt;p&gt;That&apos;s why I started Gnocchi last year, which is perfectly designed for this use case. It allows linear access time to metrics (O(1) complexity) and fast access time to the resources data via an index.&lt;/p&gt;
&lt;p&gt;Today, with 3 projects having their own perimeter of features defined – and which can work together – Ceilometer, Aodh and Gnocchi finally erased the biggest problems and defects of the initial project.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;To end with OpenStack, one last question. You&apos;re a &lt;a href=&quot;http://www.python.org/&quot;&gt;Python&lt;/a&gt; developer for a long time and a fervent user of software testing and &lt;a href=&quot;https://en.wikipedia.org/wiki/Test_driven_development&quot;&gt;test-driven development&lt;/a&gt;. Several of your blogs posts point how important their usage are. Can you tell us more about the usage of tests in OpenStack, and the test prerequisites to contribute to OpenStack?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I don&apos;t know any project that is as tested on every layer as OpenStack is. At the start of the project, there was a vague test coverage, made of a few unit tests. For each release, a bunch of new features were provided, and you had to keep your fingers crossed to have them working. That&apos;s already almost unacceptable. But the big issue was that there was also a lot of regressions, et things that were working were not anymore. It was often corner cases that developers forgot about that stopped working.&lt;/p&gt;
&lt;p&gt;Then the project decided to change its policy and started to refuse all patches – new features or bug fix – that would not implement a minimal set of unit tests, proving the patch would work. Quickly, regressions were history, and the number of bugs largely reduced months after months.&lt;/p&gt;
&lt;p&gt;Then came the functional tests, with the &lt;a href=&quot;http://launchpad.net/tempest&quot;&gt;Tempest&lt;/a&gt; project, which runs a test battery on a complete OpenStack deployment.&lt;/p&gt;
&lt;p&gt;OpenStack now possesses a &lt;a href=&quot;http://status.openstack.org/zuul/&quot;&gt;complete test infrastructure&lt;/a&gt;, with operators hired full-time to maintain them. The developers have to write the test, and the operators maintain an architecture based on Gerrit, Zuul, and Jenkins, which runs the test battery of each project for each patch sent.&lt;/p&gt;
&lt;p&gt;Indeed, for each version of a patch sent, a full OpenStack is deployed into a virtual machine, and a battery of thousands of unit and functional tests is run to check that no regressions are possible.&lt;/p&gt;
&lt;p&gt;To contribute to OpenStack, you need to know how to write a unit test – the policy on functional tests is laxer. The tools used are standard Python tools, unittest for the framework and &lt;a href=&quot;https://pypi.python.org/pypi/tox&quot;&gt;tox&lt;/a&gt; to run a virtual environment (venv) and run them.&lt;/p&gt;
&lt;p&gt;It&apos;s also possible to use &lt;a href=&quot;http://docs.openstack.org/developer/devstack/&quot;&gt;DevStack&lt;/a&gt; to deploy an OpenStack platform on a virtual machine and run functional tests. However, since the project infrastructure also do that when a patch is submitted, it&apos;s not mandatory to do that yourself locally.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The tools and tests you write for OpenStack are written in Python, a language which is very popular today. You seem to like it more than you have to, since you wrote a book about it, &lt;a href=&quot;https://thehackerguidetopython.com&quot;&gt;The Hacker&apos;s Guide to Python&lt;/a&gt;, that I really enjoyed. Can you explain what brought you to Python, the main strong points you attribute to this language (quickly) and how you went from developer to author?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I stumbled upon Python by chance, around 2005. I don&apos;t remember how I hear about it, but I bought a first book to discover it and started toying with that language. At that time, I didn&apos;t find any project to contribute to or to start. My first project with Python was rebuildd for Debian in 2007, a bit later.&lt;/p&gt;
&lt;p&gt;I like Python for its simplicity, its object orientation rather clean, its easiness to be deployed and its rich open source ecosystem. Once you get the basics, it&apos;s very easy to evolve and to use it for anything, because the ecosystem makes it easy to find libraries to solve any kind of problem.&lt;/p&gt;
&lt;p&gt;I became an author by chance, writing blog posts from time to time about Python. I finally realized that after a few years studying Python internals (CPython), I learned a lot of things. While writing a post about&lt;br /&gt;
&lt;a href=&quot;https://julien.danjou.info/blog/2013/guide-python-static-class-abstract-methods&quot;&gt;the differences between method types in Python&lt;/a&gt; – which is still one of the most read post on my blog – I realized that a lot of things that seemed obvious to me where not for other developers.&lt;/p&gt;
&lt;p&gt;I wrote that initial post after thousands of hours spent doing code reviews on OpenStack. I, therefore, decided to note all the developers pain points and to write a book about that. A compilation of what years of experience taught me and taught to the other developers I decided to interview in the book.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I&apos;ve been very interested by the publication of your book, for the subject itself, but also the process you chose. You self-published the book, which seems very relevant nowadays. Is that a choice from the start? Did you look for an editor? Can you tell use more about that?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&apos;ve been lucky to find out about others self-published authors, such as &lt;a href=&quot;http://nathanbarry.com/&quot;&gt;Nathan Barry&lt;/a&gt; – who even wrote a book on that subject, called &lt;a href=&quot;http://nathanbarry.com/authority/&quot;&gt;Authority&lt;/a&gt;. That&apos;s what convinced me it was possible and gave me hints for that project.&lt;/p&gt;
&lt;p&gt;I&apos;ve started to write in August 2013, and I ran the firs interviews with other developers at that time. I started to write the table of contents and then filled the pages with what I knew and what I wanted to share. I manage to finish the book around January 2014. The proof-reading took more time than I expected, so the book was only released in March 2014. I wrote a &lt;a href=&quot;https://julien.danjou.info/blog/making-of-the-hacker-guide-to-python&quot;&gt;complete report&lt;/a&gt; about that on my blog, where I explain the full process in detail, from writing to launching.&lt;/p&gt;
&lt;p&gt;I did not look for editors though I&apos;ve been proposed some. The idea of self-publishing really convince me, so I decided to go on my own, and I have no regret. It&apos;s true that you have to wear two hats at the same time and handle a lot more things, but with a minimal audience and some help from the Internet, anything&apos;s possible!&lt;/p&gt;
&lt;p&gt;I&apos;ve been reached by two editors since then, a &lt;a href=&quot;http://item.jd.com/11685556.html&quot;&gt;Chinese&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/juldanjou/status/552056642322583552&quot;&gt;Korean&lt;/a&gt; one. I gave them rights to translate and publish the books in their countries, so you can buy the Chinese and Korean version of the first edition of the book out there.&lt;/p&gt;
&lt;p&gt;Seeing how successful it was, I decided to launch a second edition in May 2015, and it&apos;s likely that a third edition will be released in 2016.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Nowadays, you work for &lt;a href=&quot;http://www.redhat.com&quot;&gt;Red Hat&lt;/a&gt;, a company that represents the success of using Free Software as a commercial business model. This company fascinates a lot in our community. What can you say about your employer from your point of view?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It only has been a year since I joined Red Hat (when they bought &lt;a href=&quot;http://www.enovance.com/&quot;&gt;eNovance&lt;/a&gt;), so my experience is quite recent.&lt;/p&gt;
&lt;p&gt;Though, Red Hat is really a special company on every level. It&apos;s hard to see from the outside how open it is, and how it works. It&apos;s really close to and it really looks like an open source project. For more details, you should read &lt;a href=&quot;https://www.redhat.com/en/explore/the-open-organization-book&quot;&gt;The Open Organization&lt;/a&gt;, a book wrote by Jim Whitehurst (CEO of Red Hat), which he just published. It describes perfectly how Red Hat works. To summarize, meritocracy and the lack of organization in silos is what makes Red Hat a strong organization and puts them as&lt;br /&gt;
&lt;a href=&quot;http://www.forbes.com/innovative-companies/list/&quot;&gt;one of the most innovative company&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the end, I&apos;m lucky enough to be autonomous for the project I work on with my team around OpenStack, and I can spend 100% working upstream and enhance the Python ecosystem.&lt;/p&gt;
</content:encoded></item><item><title>Visualize your OpenStack cloud: Gnocchi &amp; Grafana</title><link>https://julien.danjou.info/blog/openstack-gnocchi-grafana/</link><guid isPermaLink="true">https://julien.danjou.info/blog/openstack-gnocchi-grafana/</guid><description>We&apos;ve been hard working with the Gnocchi team these last months to store your metrics, and I guess it&apos;s time to show off a bit.  So far Gnocchi offers scalable metric storage and resource indexation,</description><pubDate>Mon, 14 Sep 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We&apos;ve been hard working with the Gnocchi team these last months to store your metrics, and I guess it&apos;s time to show off a bit.&lt;/p&gt;
&lt;p&gt;So far Gnocchi offers scalable metric storage and resource indexation, especially for OpenStack cloud – but not only, we&apos;re generic. It&apos;s cool to store metrics, but it can be even better to have a way to visualize them!&lt;/p&gt;
&lt;h2&gt;Prototyping&lt;/h2&gt;
&lt;p&gt;We very soon started to build a little HTML interface. Being REST-friendly guys, we enabled it on the same endpoints that were being used to retrieve information and measures about metric, sending back &lt;code&gt;text/html&lt;/code&gt; instead of &lt;code&gt;application/json&lt;/code&gt; if you were requesting those pages from a Web browser.&lt;/p&gt;
&lt;p&gt;But let&apos;s face it: we are back-end developers, we suck at any kind front-end development. CSS, HTML, JavaScript? Bwah! So what we built was a starting point, hoping some magical Web developer would jump in and finish the job.&lt;/p&gt;
&lt;p&gt;Obviously it never happened.&lt;/p&gt;
&lt;h2&gt;Ok, so what&apos;s out there?&lt;/h2&gt;
&lt;p&gt;It turns out there are back-end agnostic solutions out there, and we decided to pick &lt;a href=&quot;http://grafana.org&quot;&gt;Grafana&lt;/a&gt;. Grafana is a complete graphing dashboard solution that can be plugged on top of any back-end. It already supports timeseries databases such as Graphite, InfluxDB and OpenTSDB.&lt;/p&gt;
&lt;p&gt;That was largely enough for that my fellow developer &lt;a href=&quot;https://blog.sileht.net/&quot;&gt;Mehdi Abaakouk&lt;/a&gt; to jump in and start writing a Gnocchi plugin for Grafana! Consequently, there is now a basic but solid and working back-end for Grafana that lies in the &lt;em&gt;&lt;a href=&quot;https://github.com/grafana/grafana-plugins/tree/master/datasources/gnocchi&quot;&gt;grafana-plugins&lt;/a&gt;&lt;/em&gt;&lt;br /&gt;
repository.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-grafana.png&quot; alt=&quot;gnocchi-grafana&quot; /&gt;&lt;/p&gt;
&lt;p&gt;With that plugin, you can graph anything that is stored in Gnocchi, from raw metrics to metrics tied to resources. You can use templating, but no annotation yet.&lt;/p&gt;
&lt;p&gt;The back-end supports Gnocchi with or without Keystone involved, and any type of authentication (basic auth or Keystone token). So yes, it even works if you&apos;re not running Gnocchi with the rest of OpenStack.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-grafana-group.png&quot; alt=&quot;gnocchi-grafana-group&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It also supports advanced queries, so you can search for resources based on some criterion and graphs their metrics.&lt;/p&gt;
&lt;h2&gt;I want to try it!&lt;/h2&gt;
&lt;p&gt;If you want to deploy it, all you need to do is to install Grafana and its plugins, and create a new datasource pointing to Gnocchi. It is that simple. There&apos;s some CORS middleware configuration involved if you&apos;re planning on using Keystone authentication, but it&apos;s pretty straightforward – just set the &lt;code&gt;cors.allowed_origin&lt;/code&gt; option to the URL of your Grafana dashboard.&lt;/p&gt;
&lt;p&gt;We added support of Grafana directly in Gnocchi devstack plugin. If you&apos;re running &lt;a href=&quot;http://devstack.org&quot;&gt;DevStack&lt;/a&gt; you can follow &lt;a href=&quot;http://docs.openstack.org/developer/gnocchi/devstack.html&quot;&gt;the instructions&lt;/a&gt; – which are basically adding the line &lt;code&gt;enable_service gnocchi-grafana&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Moving to Grafana core&lt;/h2&gt;
&lt;p&gt;[Mehdi just opened a pull request] (&lt;a href=&quot;https://github.com/grafana/grafana/pull/2716&quot;&gt;https://github.com/grafana/grafana/pull/2716&lt;/a&gt;) a few days ago to merge the plugin into Grafana core. It&apos;s actually one of the most unit-tested plugin in Grafana so far, so it should be on a good path to be merged in the future and have support of Gnocchi directly into Grafana without any plugin involved.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/grafana-gnocchi-unittests.png&quot; alt=&quot;grafana-gnocchi-unittests&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>Data validation in Python with voluptuous</title><link>https://julien.danjou.info/blog/python-schema-validation-voluptuous/</link><guid isPermaLink="true">https://julien.danjou.info/blog/python-schema-validation-voluptuous/</guid><description>Continuing my post series on the tools I use these days in Python, this time I would like to talk about a library I really like, named voluptuous.</description><pubDate>Fri, 04 Sep 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Continuing my post series on the tools I use these days in Python, this time I would like to talk about a library I really like, named &lt;em&gt;&lt;a href=&quot;https://pypi.python.org/pypi/voluptuous&quot;&gt;voluptuous&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;It&apos;s no secret that most of the time, when a program receives data from the outside, it&apos;s a big deal to handle it. Indeed, most of the time your program has no guarantee that the stream is valid and that it contains what is expected.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://en.wikipedia.org/wiki/Robustness_principle&quot;&gt;robustness principle&lt;/a&gt; says you should be liberal in what you accept, though &lt;a href=&quot;http://cacm.acm.org/magazines/2011/8/114933-the-robustness-principle-reconsidered/fulltext&quot;&gt;that is not always a good idea&lt;/a&gt; neither. Whatever policy you chose, you need to process those data and implement a policy that will work – lax or not.&lt;/p&gt;
&lt;p&gt;That means that the program need to look into the data received, check that it finds everything it needs, complete what might be missing (e.g. set some default), transform some data, and maybe reject those data in the end.&lt;/p&gt;
&lt;h2&gt;Data validation&lt;/h2&gt;
&lt;p&gt;The first step is to validate the data, which means checking all the fields are there and all the types are right or understandable (parseable). &lt;em&gt;Voluptuous&lt;/em&gt; provides a single interface for all that called a &lt;code&gt;Schema&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; from voluptuous import Schema
&amp;gt;&amp;gt;&amp;gt; s = Schema({
...   &apos;q&apos;: str,
...   &apos;per_page&apos;: int,
...   &apos;page&apos;: int,
... })
&amp;gt;&amp;gt;&amp;gt; s({&quot;q&quot;: &quot;hello&quot;})
{&apos;q&apos;: &apos;hello&apos;}
&amp;gt;&amp;gt;&amp;gt; s({&quot;q&quot;: &quot;hello&quot;, &quot;page&quot;: &quot;world&quot;})
voluptuous.MultipleInvalid: expected int for dictionary value @ data[&apos;page&apos;]
&amp;gt;&amp;gt;&amp;gt; s({&quot;q&quot;: &quot;hello&quot;, &quot;unknown&quot;: &quot;key&quot;})
voluptuous.MultipleInvalid: extra keys not allowed @ data[&apos;unknown&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The argument to &lt;code&gt;voluptuous.Schema&lt;/code&gt; should be the data structure that you expect. &lt;em&gt;Voluptuous&lt;/em&gt; accepts any kind of data structure, so it could also be a simple string or an array of dict of array of integer. You get it. Here it&apos;s a &lt;code&gt;dict&lt;/code&gt; with a few keys that if present should be validated as certain types. By default, &lt;em&gt;Voluptuous&lt;/em&gt; does not raise an error if some keys are missing. However, it is invalid to have extra keys in a dict by default. If you want to allow extra keys, it is possible to specify it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; from voluptuous import Schema
&amp;gt;&amp;gt;&amp;gt; s = Schema({&quot;foo&quot;: str}, extra=True)
&amp;gt;&amp;gt;&amp;gt; s({&quot;bar&quot;: 2})
{&quot;bar&quot;: 2}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is also possible to make some keys mandatory.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; from voluptuous import Schema, Required
&amp;gt;&amp;gt;&amp;gt; s = Schema({Required(&quot;foo&quot;): str})
&amp;gt;&amp;gt;&amp;gt; s({})
voluptuous.MultipleInvalid: required key not provided @ data[&apos;foo&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can create custom data type very easily. &lt;em&gt;Voluptuous&lt;/em&gt; data types are actually just functions that are called with one argument, the value, and that should either return the value or raise an &lt;code&gt;Invalid&lt;/code&gt; or &lt;code&gt;ValueError&lt;/code&gt; exception.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; from voluptuous import Schema, Invalid
&amp;gt;&amp;gt;&amp;gt; def StringWithLength5(value):
...     if isinstance(value, str) and len(value) == 5:
...             return value
...     raise Invalid(&quot;Not a string with 5 chars&quot;)
...
&amp;gt;&amp;gt;&amp;gt; s = Schema(StringWithLength5)
&amp;gt;&amp;gt;&amp;gt; s(&quot;hello&quot;)
&apos;hello&apos;
&amp;gt;&amp;gt;&amp;gt; s(&quot;hello world&quot;)
voluptuous.MultipleInvalid: Not a string with 5 chars
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Most of the time though, there is no need to create your own data types. &lt;em&gt;Voluptuous&lt;/em&gt; provides logical operators that can, combined with a few others provided primitives such as &lt;code&gt;voluptuous.Length&lt;/code&gt; or &lt;code&gt;voluptuous.Range&lt;/code&gt;, create a large range of validation scheme.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; from voluptuous import Schema, Length, All
&amp;gt;&amp;gt;&amp;gt; s = Schema(All(str, Length(min=3, max=5)))
&amp;gt;&amp;gt;&amp;gt; s(&quot;hello&quot;)
&quot;hello&quot;
&amp;gt;&amp;gt;&amp;gt; s(&quot;hello world&quot;)
voluptuous.MultipleInvalid: length of value must be at most 5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a href=&quot;https://pypi.python.org/pypi/voluptuous&quot;&gt;voluptuous documentation&lt;/a&gt; has a good set of examples that you can check to have a good overview of what you can do.&lt;/p&gt;
&lt;h2&gt;Data transformation&lt;/h2&gt;
&lt;p&gt;What&apos;s important to remember, is that each data type that you use is a function that is called and returns a value, if the value is considered valid. That value returned is what is actually used and returned after the schema validation:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; import uuid
&amp;gt;&amp;gt;&amp;gt; from voluptuous import Schema
&amp;gt;&amp;gt;&amp;gt; def UUID(value):
...     return uuid.UUID(value)
...
&amp;gt;&amp;gt;&amp;gt; s = Schema({&quot;foo&quot;: UUID})
&amp;gt;&amp;gt;&amp;gt; data_converted = s({&quot;foo&quot;: &quot;uuid?&quot;})
voluptuous.MultipleInvalid: not a valid value for dictionary value @ data[&apos;foo&apos;]
&amp;gt;&amp;gt;&amp;gt; data_converted = s({&quot;foo&quot;: &quot;8B7BA51C-DFF5-45DD-B28C-6911A2317D1D&quot;})
&amp;gt;&amp;gt;&amp;gt; data_converted
{&apos;foo&apos;: UUID(&apos;8b7ba51c-dff5-45dd-b28c-6911a2317d1d&apos;)}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By defining a custom &lt;code&gt;UUID&lt;/code&gt; function that converts a value to a UUID, the schema converts the string passed in the data to a Python UUID object – validating the format at the same time.&lt;/p&gt;
&lt;p&gt;Note a little trick here: it&apos;s not possible to use directly &lt;code&gt;uuid.UUID&lt;/code&gt; in the schema, otherwise &lt;em&gt;Voluptuous&lt;/em&gt; would check that the data is actually an instance of &lt;code&gt;uuid.UUID&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; from voluptuous import Schema
&amp;gt;&amp;gt;&amp;gt; s = Schema({&quot;foo&quot;: uuid.UUID})
&amp;gt;&amp;gt;&amp;gt; s({&quot;foo&quot;: &quot;8B7BA51C-DFF5-45DD-B28C-6911A2317D1D&quot;})
voluptuous.MultipleInvalid: expected UUID for dictionary value @ data[&apos;foo&apos;]
&amp;gt;&amp;gt;&amp;gt; s({&quot;foo&quot;: uuid.uuid4()})
{&apos;foo&apos;: UUID(&apos;60b6d6c4-e719-47a7-8e2e-b4a4a30631ed&apos;)}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that&apos;s not what is wanted here.&lt;/p&gt;
&lt;p&gt;That mechanism is really neat to transform, for example, strings to timestamps.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; import datetime
&amp;gt;&amp;gt;&amp;gt; from voluptuous import Schema
&amp;gt;&amp;gt;&amp;gt; def Timestamp(value):
...     return datetime.datetime.strptime(value, &quot;%Y-%m-%dT%H:%M:%S&quot;)
...
&amp;gt;&amp;gt;&amp;gt; s = Schema({&quot;foo&quot;: Timestamp})
&amp;gt;&amp;gt;&amp;gt; s({&quot;foo&quot;: &apos;2015-03-03T12:12:12&apos;})
{&apos;foo&apos;: datetime.datetime(2015, 3, 3, 12, 12, 12)}
&amp;gt;&amp;gt;&amp;gt; s({&quot;foo&quot;: &apos;2015-03-03T12:12&apos;})
voluptuous.MultipleInvalid: not a valid value for dictionary value @ data[&apos;foo&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Recursive schemas&lt;/h2&gt;
&lt;p&gt;So far, &lt;em&gt;Voluptuous&lt;/em&gt; has one limitation so far: the ability to have recursive schemas. The simplest way to circumvent it is by using another function as an indirection.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; from voluptuous import Schema, Any
&amp;gt;&amp;gt;&amp;gt; def _MySchema(value):
...     return MySchema(value)
...
&amp;gt;&amp;gt;&amp;gt; from voluptuous import Any
&amp;gt;&amp;gt;&amp;gt; MySchema = Schema({&quot;foo&quot;: Any(&quot;bar&quot;, _MySchema)})
&amp;gt;&amp;gt;&amp;gt; MySchema({&quot;foo&quot;: {&quot;foo&quot;: &quot;bar&quot;}})
{&apos;foo&apos;: {&apos;foo&apos;: &apos;bar&apos;}}
&amp;gt;&amp;gt;&amp;gt; MySchema({&quot;foo&quot;: {&quot;foo&quot;: &quot;baz&quot;}})
voluptuous.MultipleInvalid: not a valid value for dictionary value @ data[&apos;foo&apos;][&apos;foo&apos;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Usage in REST API&lt;/h2&gt;
&lt;p&gt;I started to use &lt;em&gt;Voluptuous&lt;/em&gt; to validate data in a the REST API provided by &lt;a href=&quot;http://launchpad.net/gnocchi&quot;&gt;Gnocchi&lt;/a&gt;. So far it has been a really good tool, and we&apos;ve been able to &lt;a href=&quot;http://docs.openstack.org/developer/gnocchi/rest.html&quot;&gt;create a complete REST API&lt;/a&gt; that is very easy to validate on the server side. I would definitely recommend it for that. It blends with any Web framework easily.&lt;/p&gt;
&lt;p&gt;One of the upside compared to solution like &lt;a href=&quot;http://json-schema.org/&quot;&gt;JSON Schema&lt;/a&gt;, is the ability to create or re-use your own custom data types while converting values at validation time. It is also very Pythonic, and extensible – it&apos;s pretty great to use for all of that. It&apos;s also not tied to any serialization format.&lt;/p&gt;
&lt;p&gt;On the other hand, JSON Schema is language agnostic and is serializable itself as JSON. That makes it easy to be exported and provided to a consumer so it can understand the API and validate the data potentially on its side.&lt;/p&gt;
</content:encoded></item><item><title>Reading LWN.net with Pocket</title><link>https://julien.danjou.info/blog/announcing-lwn2pocket/</link><guid isPermaLink="true">https://julien.danjou.info/blog/announcing-lwn2pocket/</guid><description>I&apos;ve started to use Pocket a few months ago to store my backlog of things to read. It&apos;s especially useful as I can use it to read content offline since we still don&apos;t have any Internet access in.</description><pubDate>Thu, 13 Aug 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve started to use &lt;a href=&quot;https://pocket.co&quot;&gt;Pocket&lt;/a&gt; a few months ago to store my backlog of things to read. It&apos;s especially useful as I can use it to read content offline since we still don&apos;t have any Internet access in places such as airplanes or the Paris metro. It&apos;s only 2015 after all.&lt;/p&gt;
&lt;p&gt;I am also a &lt;a href=&quot;http://lwn.net&quot;&gt;LWN.net&lt;/a&gt; subscriber for years now, and I really like their articles from the weekly edition. Unfortunately, as the access is restricted to subscribers, you need to login: it makes it impossible to add these articles to Pocket directly. Sad.&lt;/p&gt;
&lt;p&gt;Yesterday, I thought about that and decided to start hacking on it. LWN provides a feature called &quot;Subscriber Link&quot; that allows you to share an article with a friend. I managed to use that feature to share the articles with my friend… Pocket!&lt;/p&gt;
&lt;p&gt;As doing that every week is tedious, I wrote a small Python program called &lt;a href=&quot;https://github.com/jd/lwn2pocket&quot;&gt;lwn2pocket&lt;/a&gt; that I published on &lt;a href=&quot;http://github.com&quot;&gt;GitHub&lt;/a&gt;. Feel free to use it, hack it and send pull requests.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/lwn2pocket.png&quot; alt=&quot;lwn2pocket&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>Ceilometer, Gnocchi &amp; Aodh: Liberty progress</title><link>https://julien.danjou.info/blog/ceilometer-gnocchi-aodh-liberty-progress/</link><guid isPermaLink="true">https://julien.danjou.info/blog/ceilometer-gnocchi-aodh-liberty-progress/</guid><description>It&apos;s been a while since I talked about Ceilometer and its companions, so I thought I&apos;d go ahead and write a bit about what&apos;s going on this side of OpenStack. I&apos;m not going to cover new features and fa</description><pubDate>Tue, 04 Aug 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It&apos;s been a while since I talked about Ceilometer and its companions, so I thought I&apos;d go ahead and write a bit about what&apos;s going on this side of OpenStack. I&apos;m not going to cover new features and fancy stuff today, but rather a shallow overview of the new project processes we initiated.&lt;/p&gt;
&lt;h2&gt;Ceilometer growing&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://launchpad.net/ceilometer&quot;&gt;Ceilometer&lt;/a&gt; has grown a lot since that time when we started it 3 years ago. It has evolved from a system designed to fetch and store measurements, to a more complex system, with agents, alarms, events, databases, APIs, etc.&lt;/p&gt;
&lt;p&gt;All those features were needed and asked for by users and operators, but let&apos;s be honest, some of them should never have ended up in the Ceilometer code repository, especially not all at the same time.&lt;/p&gt;
&lt;p&gt;The reality is we picked a pragmatic approach due to the rigidity of the OpenStack Technical Committee in regards to new projects to become OpenStack integrated – and, therefore, blessed – projects. Ceilometer was actually the first project to be incubated and then integrated. We had to go through the very first issues of that process.&lt;/p&gt;
&lt;p&gt;Fortunately, now that time has passed, and all those constraints have been relaxed. To me, the &lt;a href=&quot;https://www.openstack.org/foundation&quot;&gt;OpenStack Foundation&lt;/a&gt; is turning into something that looks like the &lt;a href=&quot;http://www.apache.org/foundation/&quot;&gt;Apache Foundation&lt;/a&gt;, and there&apos;s, therefore, no need to tie technical solutions to political issues.&lt;/p&gt;
&lt;p&gt;Indeed, the &lt;a href=&quot;https://www.openstack.org/summit/vancouver-2015/summit-videos/presentation/the-big-tent-a-look-at-the-new-openstack-projects-governance&quot;&gt;Big Tent&lt;/a&gt; now allows much more flexibility to all of that. Back a year ago, we were afraid to bring Gnocchi into Ceilometer. Was the Technical Committee going to review the project? Was the project going to be in the scope of Ceilometer for the Technical Committee? Now we don&apos;t have to ask ourselves those questions, now that we have that freedom, it empowers us to actually do what we think is good in term of technical design without worrying too much about political issues.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/ceilometer-activity.png&quot; alt=&quot;ceilometer-activity&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Acknowledging Gnocchi&lt;/h2&gt;
&lt;p&gt;The first step in this new process was to continue working on &lt;a href=&quot;https://launchpad.net/gnocchi&quot;&gt;Gnocchi&lt;/a&gt; (a timeserie database and resource indexer designed to overcome historical Ceilometer storage issue) and to decide that it was not the right call to merge it into Ceilometer as some REST API v3, but that it was better to keep it standalone.&lt;/p&gt;
&lt;p&gt;We managed to get traction to Gnocchi, getting a few contributors and users. We&apos;re even seeing talks proposed to the next Tokyo Summit where people leverage Gnocchi, such as &quot;Service of predictive analytics on cost and performance in OpenStack&quot;, &quot;&lt;a href=&quot;https://wiki.openstack.org/wiki/Surveil&quot;&gt;Suveil&lt;/a&gt;&quot; and &quot;Cutting Edge NFV On OpenStack: Healing and Scaling Distributed Applications&quot;.&lt;/p&gt;
&lt;p&gt;We are also doing some progress on pushing Gnocchi outside of the OpenStack community, as it can be a self-sufficient timeserie and resource database that can be used without any OpenStack interaction.&lt;/p&gt;
&lt;h2&gt;Branching Aodh&lt;/h2&gt;
&lt;p&gt;Rather than continuing to grow Ceilometer, during the last summit we all decided that it was time to reorganize and split Ceilometer into the different components it is made of, leveraging a more &lt;a href=&quot;https://en.wikipedia.org/wiki/Service-oriented_architecture&quot;&gt;service-oriented architecture&lt;/a&gt;. The alarm subsystem of Ceilometer being mostly untied to the rest of Ceilometer, we decided it was the first and perfect candidate to do that. I personally engaged into doing the work and created a new repository with only the alarm code from Ceilometer, named &lt;a href=&quot;https://launchpad.net/aodh&quot;&gt;Aodh&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/woman-fire.jpg&quot; alt=&quot;woman-fire&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This made sense for a lot of reason. First because Aodh can now work completely standalone, using either Ceilometer or Gnocchi as a backend – or any new plugin you&apos;d write. I love the idea that OpenStack projects can work standalone – like Swift does for example – without implying any other OpenStack component. I think it&apos;s a proof of good design. Secondly, because it allows us to resonate on a smaller chunk of software – a reason really under-estimated today in OpenStack. I believe that the size of your software should match a certain ratio to the size of your team.&lt;/p&gt;
&lt;p&gt;Aodh is, therefore, a new project under the OpenStack Telemetry program (or what remains of OpenStack programs now), alongside Ceilometer and Gnocchi, forked from the original Ceilometer alarm feature. We&apos;ll deprecate the latter with the Liberty release, and we&apos;ll remove it in the Mitaka release.&lt;/p&gt;
&lt;h2&gt;Lessons learned&lt;/h2&gt;
&lt;p&gt;Actually, moving that code out of Ceilometer (in the case of Aodh), or not merging it in (in the case of Gnocchi) had a few side effects that I admit I think we probably under-estimated back then.&lt;/p&gt;
&lt;p&gt;Indeed, the code size of Gnocchi or Aodh ended up being much smaller than the entire Ceilometer project – Gnocchi is 7× smaller and Aodh 5x smaller than Ceilometer – and therefore much more easy to manipulate and to hack on. That allowed us to merge dozens of patches in a few weeks, cleaning-up and enhancing a lot of small things in the code. Those tasks are very much harder in Ceilometer, due to the bigger size of the code base and the small size of our team. By having our small team working on smaller chunks of changes – even when it meant actually doing more reviews – greatly improved our general velocity and the number of bugs fixed and features implemented.&lt;/p&gt;
&lt;p&gt;On the more sociological side, I think it gave the team the sensation of finally owning the project. Ceilometer was huge, and it was impossible for people to know every side of it. Now, it&apos;s getting possible for people inside a team to cover a much larger portion of those smaller project, which gives them a greater sense of ownership and caring. Which ends up being good for the project quality overall.&lt;/p&gt;
&lt;p&gt;That also means that we technically decided to have different core teams by project (Ceilometer, Gnocchi, and Aodh) as they all serve different purposes and can all be used standalone or with each others. Meaning we could have contributors completely ignoring other projects.&lt;/p&gt;
&lt;p&gt;All of that reminds me some discussion I heard about projects such as Glance, trying to fit new features in - some that are really orthogonal to the original purpose. It&apos;s now clear to me that having different small components interacting together that can be completely owned and taken care of by a (small) team of contributors is the way to go. People that can therefore trust each others and easily bring new people in, makes a project really incredibly more powerful. Having a project covering a too wide set of features make things more difficult if you don&apos;t have enough manpower. This is clearly an issue that big projects inside OpenStack are facing now, such as Neutron or Nova.&lt;/p&gt;
</content:encoded></item><item><title>Timezones and Python</title><link>https://julien.danjou.info/blog/python-and-timezones/</link><guid isPermaLink="true">https://julien.danjou.info/blog/python-and-timezones/</guid><description>Recently, I&apos;ve been fighting with the never ending issue of timezones. I never thought I would have plunged into this rabbit hole, but hacking on OpenStack and Gnocchi I felt into that trap easily is,</description><pubDate>Tue, 16 Jun 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recently, I&apos;ve been fighting with the never ending issue of timezones. I never thought I would have plunged into this rabbit hole, but hacking on OpenStack and Gnocchi I felt into that trap easily is, thanks to Python.&lt;/p&gt;
&lt;h2&gt;“Why you really, really, should never ever deal with timezones”&lt;/h2&gt;
&lt;p&gt;To get a glimpse of the complexity of timezones, I recommend that you watch &lt;a href=&quot;http://www.tomscott.com/&quot;&gt;Tom Scott&lt;/a&gt;&apos;s video on the subject. It&apos;s fun and it summarizes remarkably well the nightmare that timezones are and why you should stop thinking that you&apos;re smart.&lt;/p&gt;
&lt;h2&gt;The importance of timezones in applications&lt;/h2&gt;
&lt;p&gt;Once you&apos;ve heard what Tom says, I think it gets pretty clear that a timestamp without any timezone attached does not give any useful information. It should be considered irrelevant and useless. Without the necessary context given by the timezone, you cannot infer what point in time your application is really referring to.&lt;/p&gt;
&lt;p&gt;That means your application should never handle timestamps with no timezone information. It should try to guess or raises an error if no timezone is provided in any input.&lt;/p&gt;
&lt;p&gt;Of course, you can infer that having no timezone information means UTC. This sounds very handy, but can also be dangerous in certain applications or language – such as Python, as we&apos;ll see.&lt;/p&gt;
&lt;p&gt;Indeed, in certain applications, converting timestamps to UTC and losing the timezone information is a terrible idea. Imagine that a user create a recurring event every Wednesday at 10:00 in its local timezone, say CET. If you convert that to UTC, the event will end up being stored as every Wednesday at 09:00.&lt;/p&gt;
&lt;p&gt;Now imagine that the CET timezone switches from UTC+01:00 to UTC+02:00: your application will compute that the event starts at 11:00 CET every Wednesday. Which is wrong, because as the user told you, the event starts at 10:00 CET, whatever the definition of CET is. Not at 11:00 CET. So CET means CET, not necessarily UTC+1.&lt;/p&gt;
&lt;p&gt;As for endpoints like REST API, a thing I daily deal with, all timestamps should include a timezone information. It&apos;s nearly impossible to know what timezone the timestamps are in otherwise: UTC? Server local? User local? No way to know.&lt;/p&gt;
&lt;h2&gt;Python design &amp;amp; defect&lt;/h2&gt;
&lt;p&gt;Python comes with a timestamp object named &lt;code&gt;datetime.datetime&lt;/code&gt;. It can store date and time precise to the microsecond, and is qualified of timezone &quot;aware&quot; or &quot;unaware&quot;, whether it embeds a timezone information or not.&lt;/p&gt;
&lt;p&gt;To build such an object based on the current time, one can use &lt;code&gt;datetime.datetime.utcnow()&lt;/code&gt; to retrieve the date and time for the UTC timezone, and &lt;code&gt;datetime.datetime.now()&lt;/code&gt; to retrieve the date and time for the current timezone, whatever it is.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; import datetime
&amp;gt;&amp;gt;&amp;gt; datetime.datetime.utcnow()
datetime.datetime(2015, 6, 15, 13, 24, 48, 27631)
&amp;gt;&amp;gt;&amp;gt; datetime.datetime.now()
datetime.datetime(2015, 6, 15, 15, 24, 52, 276161)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can notice, none of these results contains timezone information. Indeed, Python &lt;code&gt;datetime&lt;/code&gt; API always returns unaware &lt;code&gt;datetime&lt;/code&gt; objects, which is very unfortunate. Indeed, as soon as you get one of this object, there is no way to know what the timezone is, therefore these objects are pretty &quot;useless&quot; on their own.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://lucumr.pocoo.org/2011/7/15/eppur-si-muove/&quot;&gt;Armin Ronacher proposes that an application always consider that the unaware &lt;code&gt;datetime&lt;/code&gt; objects from Python are considered as UTC&lt;/a&gt;. As we just saw, that statement cannot be considered true for objects returned by &lt;code&gt;datetime.datetime.now()&lt;/code&gt;, so I would not advise doing so. &lt;code&gt;datetime&lt;/code&gt; objects with no timezone should be considered as a &quot;bug&quot; in the application.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/timezone-map.jpg&quot; alt=&quot;timezone-map&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Recommendations&lt;/h2&gt;
&lt;p&gt;My recommendation list comes down to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Always use aware &lt;code&gt;datetime&lt;/code&gt; object, i.e. with timezone information. That makes sure you can compare them directly (aware and unaware &lt;code&gt;datetime&lt;/code&gt; objects are not comparable) and will return them correctly to users. Leverage &lt;a href=&quot;http://pytz.sourceforge.net/&quot;&gt;pytz&lt;/a&gt; to have timezone objects.&lt;/li&gt;
&lt;li&gt;Use &lt;a href=&quot;https://en.wikipedia.org/wiki/ISO_8601&quot;&gt;ISO 8601&lt;/a&gt; as input and output string format. Use &lt;code&gt;datetime.datetime.isoformat()&lt;/code&gt; to return timestamps as string formatted using that format, which includes the timezone information.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In Python, that&apos;s equivalent to having:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; import datetime
&amp;gt;&amp;gt;&amp;gt; import pytz
&amp;gt;&amp;gt;&amp;gt; def utcnow():
    return datetime.datetime.now(tz=pytz.utc)
&amp;gt;&amp;gt;&amp;gt; utcnow()
datetime.datetime(2015, 6, 15, 14, 45, 19, 182703, tzinfo=&amp;lt;UTC&amp;gt;)
&amp;gt;&amp;gt;&amp;gt; utcnow().isoformat()
&apos;2015-06-15T14:45:21.982600+00:00&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you need to parse strings containing ISO 8601 formatted timestamp, you can rely on the &lt;em&gt;&lt;a href=&quot;https://pypi.python.org/pypi/iso8601&quot;&gt;iso8601&lt;/a&gt;&lt;/em&gt;, which returns timestamps with correct timezone information. This makes timestamps directly comparable:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; import iso8601
&amp;gt;&amp;gt;&amp;gt; iso8601.parse_date(utcnow().isoformat())
datetime.datetime(2015, 6, 15, 14, 46, 43, 945813, tzinfo=&amp;lt;FixedOffset &apos;+00:00&apos; datetime.timedelta(0)&amp;gt;)
&amp;gt;&amp;gt;&amp;gt; iso8601.parse_date(utcnow().isoformat()) &amp;lt; utcnow()
True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you need to store those timestamps, the same rule should apply. If you rely on &lt;a href=&quot;http://mongodb.org&quot;&gt;MongoDB&lt;/a&gt;, it assumes that all the timestamp are in UTC, so be careful when storing them – you will have to normalize the timestamp to UTC.&lt;/p&gt;
&lt;p&gt;For &lt;a href=&quot;http://mysql.org&quot;&gt;MySQL&lt;/a&gt;, nothing is assumed, it&apos;s up to the application to insert them in a timezone that makes sense to it. Obviously, if you have multiple applications accessing the same database with different data sources, this can end up being a nightmare.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://postgresql.org&quot;&gt;PostgreSQL&lt;/a&gt; has a &lt;a href=&quot;http://www.postgresql.org/docs/9.4/static/datatype-datetime.html&quot;&gt;special data type that is recommended&lt;/a&gt; called &lt;code&gt;timestamp with timezone&lt;/code&gt;. That does not mean you should not use UTC in most cases; that just means you are sure that the timestamp are stored in UTC since it&apos;s written in the database, and you check if any other application inserted timestamps with different timezone.&lt;/p&gt;
&lt;h2&gt;OpenStack status&lt;/h2&gt;
&lt;p&gt;As a side note, I&apos;ve improved OpenStack situation recently by changing the &lt;a href=&quot;http://docs.openstack.org/developer/oslo.utils/api/timeutils.html&quot;&gt;oslo.utils.timeutils&lt;/a&gt; module to deprecate some useless and dangerous functions. I&apos;ve also added support for returning timezone aware objects when using the &lt;code&gt;oslo_utils.timeutils.utcnow()&lt;/code&gt; function. It&apos;s not possible to make it a default unfortunately for backward compatibility reason, but it&apos;s there nevertheless, and it&apos;s advised to use it. Thanks to my colleague &lt;a href=&quot;http://haypo-notes.readthedocs.org/&quot;&gt;Victor&lt;/a&gt; for the help!&lt;/p&gt;
&lt;p&gt;Have a nice day, whatever your timezone is!&lt;/p&gt;
</content:encoded></item><item><title>Get back up and try again: retrying in Python</title><link>https://julien.danjou.info/blog/python-retrying/</link><guid isPermaLink="true">https://julien.danjou.info/blog/python-retrying/</guid><description>The library presented in this article is becoming obsolete and un-maintained. I recommend you to read this post about tenacity   instead.  I don&apos;t often write about tools I use when for my daily softw</description><pubDate>Tue, 02 Jun 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;The library presented in this article is becoming obsolete and un-maintained. I recommend you to read this post about &lt;a href=&quot;https://julien.danjou.info/blog/python-tenacity&quot;&gt;tenacity&lt;/a&gt; instead.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I don&apos;t often write about tools I use when for my daily software development tasks. I recently realized that I really should start to share more often my workflows and weapons of choice.&lt;/p&gt;
&lt;p&gt;One thing that I have a hard time enduring while doing Python code reviews, is people writing utility code that is not directly tied to the core of their business. This looks to me as wasted time maintaining code that should be reused from elsewhere.&lt;/p&gt;
&lt;p&gt;So today I&apos;d like to start with &lt;a href=&quot;https://pypi.python.org/pypi/retrying&quot;&gt;retrying&lt;/a&gt;, a Python package that you can use to… retry anything.&lt;/p&gt;
&lt;h3&gt;It&apos;s OK to fail&lt;/h3&gt;
&lt;p&gt;Often in computing, you have to deal with external resources. That means accessing resources you don&apos;t control. Resources that can fail, become flapping, unreachable or unavailable.&lt;/p&gt;
&lt;p&gt;Most applications don&apos;t deal with that at all, and explode in flight, leaving a skeptical user in front of the computer. A lot of software engineers refuse to deal with failure, and don&apos;t bother handling this kind of scenario in their code.&lt;/p&gt;
&lt;p&gt;In the best case, applications usually handle simply the case where the external reached system is out of order. They log something, and inform the user that it should try again later.&lt;/p&gt;
&lt;p&gt;In this cloud computing area, we tend to design software components with &lt;a href=&quot;https://en.wikipedia.org/wiki/Service-oriented_architecture&quot;&gt;service-oriented architecture&lt;/a&gt; in mind. That means having a lot of different services talking to each others over the network. And we all know that networks tend to fail, and distributed systems too. Writing software with failing being part of normal operation is a terrific idea.&lt;/p&gt;
&lt;h3&gt;Retrying&lt;/h3&gt;
&lt;p&gt;In order to help applications with the handling of these potential failures, you need a plan. Leaving to the user the burden to &quot;try again later&quot; is rarely a good choice. Therefore, most of the time you want your application to &lt;em&gt;retry&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Retrying an action is a full strategy on its own, with a lot of options. You can retry only on certain condition, and with the number of tries based on time (e.g. every second), based on a number of tentative (e.g. retry 3 times and abort), based on the problem encountered, or even on all of those.&lt;/p&gt;
&lt;p&gt;For all of that, I use the &lt;a href=&quot;https://github.com/rholder/retrying&quot;&gt;retrying&lt;/a&gt; library that you can retrieve easily on &lt;a href=&quot;https://pypi.python.org/pypi/retrying&quot;&gt;PyPI&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;retrying&lt;/em&gt; provides a decorator called &lt;code&gt;retry&lt;/code&gt; that you can use on top of any function or method in Python to make it retry in case of failure. By default, &lt;code&gt;retry&lt;/code&gt; calls your function endlessly until it returns rather than raising an error.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import random
from retrying import retry

@retry
def pick_one():
    if random.randint(0, 10) != 1:
        raise Exception(&quot;1 was not picked&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will execute the function &lt;code&gt;pick_one&lt;/code&gt; until &lt;code&gt;1&lt;/code&gt; is returned by &lt;code&gt;random.randint&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;retry&lt;/code&gt; accepts a few arguments, such as the minimum and maximum delays to use, which also can be randomized. Randomizing delay is a good strategy to avoid detectable pattern or congestion. But more over, it supports exponential delay, which can be used to implement &lt;a href=&quot;https://en.wikipedia.org/wiki/Exponential_backoff&quot;&gt;exponential backoff&lt;/a&gt;, a good solution for retrying tasks while really avoiding congestion. It&apos;s especially handy for background tasks.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@retry(wait_exponential_multiplier=1000, wait_exponential_max=10000)
def wait_exponential_1000():
    print &quot;Wait 2^x * 1000 milliseconds between each retry, up to 10 seconds, then 10 seconds afterwards&quot;
    raise Exception(&quot;Retry!&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can mix that with a maximum delay, which can give you a good strategy to retry for a while, and then fail anyway:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## Stop retrying after 30 seconds anyway
&amp;gt;&amp;gt;&amp;gt; @retry(wait_exponential_multiplier=1000, wait_exponential_max=10000, stop_max_delay=30000)
... def wait_exponential_1000():
...     print &quot;Wait 2^x * 1000 milliseconds between each retry, up to 10 seconds, then 10 seconds afterwards&quot;
...     raise Exception(&quot;Retry!&quot;)
...
&amp;gt;&amp;gt;&amp;gt; wait_exponential_1000()
Wait 2^x * 1000 milliseconds between each retry, up to 10 seconds, then 10 seconds afterwards
Wait 2^x * 1000 milliseconds between each retry, up to 10 seconds, then 10 seconds afterwards
Wait 2^x * 1000 milliseconds between each retry, up to 10 seconds, then 10 seconds afterwards
Wait 2^x * 1000 milliseconds between each retry, up to 10 seconds, then 10 seconds afterwards
Wait 2^x * 1000 milliseconds between each retry, up to 10 seconds, then 10 seconds afterwards
Wait 2^x * 1000 milliseconds between each retry, up to 10 seconds, then 10 seconds afterwards
Traceback (most recent call last):
  File &quot;&amp;lt;stdin&amp;gt;&quot;, line 1, in &amp;lt;module&amp;gt;
  File &quot;/usr/local/lib/python2.7/site-packages/retrying.py&quot;, line 49, in wrapped_f
    return Retrying(*dargs, **dkw).call(f, *args, **kw)
  File &quot;/usr/local/lib/python2.7/site-packages/retrying.py&quot;, line 212, in call
    raise attempt.get()
  File &quot;/usr/local/lib/python2.7/site-packages/retrying.py&quot;, line 247, in get
    six.reraise(self.value[0], self.value[1], self.value[2])
  File &quot;/usr/local/lib/python2.7/site-packages/retrying.py&quot;, line 200, in call
    attempt = Attempt(fn(*args, **kwargs), attempt_number, False)
  File &quot;&amp;lt;stdin&amp;gt;&quot;, line 4, in wait_exponential_1000
  Exception: Retry!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A pattern I use very often, is the ability to retry only based on some exception type. You can specify a function to filter out exception you want to ignore or the one you want to use to retry.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def retry_on_ioerror(exc):
    return isinstance(exc, IOError)

@retry(retry_on_exception=retry_on_ioerror)
def read_file():
    with open(&quot;myfile&quot;, &quot;r&quot;) as f:
        return f.read()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;retry&lt;/code&gt; will call the function passed as &lt;code&gt;retry_on_exception&lt;/code&gt; with the exception raised as first argument. It&apos;s up to the function to then return a boolean indicating if a retry should be performed or not. In the example above, this will only retry to read the file if an &lt;code&gt;IOError&lt;/code&gt; occurs; if any other exception type is raised, no retry will be performed.&lt;/p&gt;
&lt;p&gt;The same pattern can be implemented using the keyword argument &lt;code&gt;retry_on_result&lt;/code&gt;, where you can provide a function that analyses the result and retry based on it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def retry_if_file_empty(result):
    return len(result) &amp;lt;= 0

@retry(retry_on_result=retry_if_file_empty)
def read_file():
    with open(&quot;myfile&quot;, &quot;r&quot;) as f:
        return f.read()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This example will read the file until it stops being empty. If the file does not exist, an &lt;code&gt;IOError&lt;/code&gt; is raised, and the default behavior which triggers retry on all exceptions kicks-in – the retry is therefore performed.&lt;/p&gt;
&lt;p&gt;That&apos;s it! &lt;code&gt;retry&lt;/code&gt; is really a good and small library that you should leverage rather than implementing your own half-baked solution!&lt;/p&gt;
</content:encoded></item><item><title>OpenStack Summit Liberty from a Ceilometer &amp; Gnocchi point of view</title><link>https://julien.danjou.info/blog/openstack-summit-liberty-vancouver-ceilometer-gnocchi/</link><guid isPermaLink="true">https://julien.danjou.info/blog/openstack-summit-liberty-vancouver-ceilometer-gnocchi/</guid><description>Last week I was in Vancouver, BC for the OpenStack Summit, discussing the new Liberty version that will be released in 6 months.</description><pubDate>Tue, 26 May 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last week I was in &lt;a href=&quot;http://vancouver.ca/&quot;&gt;Vancouver, BC&lt;/a&gt; for the &lt;a href=&quot;https://www.openstack.org/summit/vancouver-2015/&quot;&gt;OpenStack Summit&lt;/a&gt;, discussing the new Liberty version that will be released in 6 months.&lt;/p&gt;
&lt;p&gt;I&apos;ve attended the summit mainly to discuss and follow-up new developments on Ceilometer, Gnocchi and Oslo. It has been a pretty good week and we were able to discuss and plan a few interesting things.&lt;/p&gt;
&lt;h2&gt;Ops feedback&lt;/h2&gt;
&lt;p&gt;We had half a dozen Ceilometer sessions, and the first one was dedicated to getting feedbacks from operators using Ceilometer. We had a few operators present, and a few of the Ceilometer team. We had constructive discussion, and my feeling is that operators struggles with 2 things so far: scaling Ceilometer storage and having Ceilometer not killing the rest of OpenStack.&lt;/p&gt;
&lt;p&gt;We discussed the first point as being addressed by &lt;a href=&quot;http://launchpad.net/gnocchi&quot;&gt;Gnocchi&lt;/a&gt;, and I presented a bit Gnocchi itself, as well as how and why it will fix the storage scalability issue operators encountered so far.&lt;/p&gt;
&lt;p&gt;Ceilometer putting down the OpenStack installation is more interesting problem. Ceilometer pollsters request information from Nova, Glance… to gather statistics. Until Kilo, Ceilometer used to do that regularly and at fixed interval, causing high pike load in OpenStack. With the &lt;a href=&quot;http://docs.openstack.org/developer/ceilometer/architecture.html#polling-agents-asking-for-data&quot;&gt;introduction of jitter&lt;/a&gt; in Kilo, this should be less of a problem. However, Ceilometer hits various endpoints in OpenStack that are poorly designed, and hitting those endpoints of Nova or other components triggers a lot of load on the platform. Unfortunately, this makes operators blame Ceilometer rather than blaming the components being guilty of poor designs. We&apos;d like to push forward improving these components, but it&apos;s probably going to take a long time.&lt;/p&gt;
&lt;h2&gt;Componentisation&lt;/h2&gt;
&lt;p&gt;When I started the Gnocchi project last year, I pretty soon realized that we would be able to split Ceilometer itself in different smaller components that could work independently, while being able to leverage each others. For example, Gnocchi can run standalone and store your metrics even if you don&apos;t use Ceilometer – nor even OpenStack itself.&lt;/p&gt;
&lt;p&gt;My fellow developer &lt;a href=&quot;http://burningchrome.com/&quot;&gt;Chris Dent&lt;/a&gt; had the same idea about splitting Ceilometer a few months ago and drafted a proposal. The idea is to have Ceilometer split in different parts that people could assemble together or run on their owns.&lt;/p&gt;
&lt;p&gt;Interestingly enough, we had three 40 minutes sessions planned to talk and debate about this division of Ceilometer, though we all agreed in 5 minutes that this was the good thing to do. Five more minutes later, we agreed on which part to split. The rest of the time was allocated to discuss various details of that split, and I engaged to start doing the work with Ceilometer alarming subsystem.&lt;/p&gt;
&lt;p&gt;I wrote a &lt;a href=&quot;https://review.openstack.org/#/c/184307/&quot;&gt;specification&lt;/a&gt; on the plane bringing me to Vancouver, that should be approved pretty soon now. I already started doing the implementation work. So fingers crossed, Ceilometer should have a new components in Liberty handling alarming on its own.&lt;/p&gt;
&lt;p&gt;This would allow users for example to only deploys Gnocchi and Ceilometer alarm. They would be able to feed data to Gnocchi using their own system, and build alarms using Ceilometer alarm subsystem relying on Gnocchi&apos;s data.&lt;/p&gt;
&lt;h2&gt;Gnocchi&lt;/h2&gt;
&lt;p&gt;We didn&apos;t have a Gnocchi dedicated slot – mainly because I indicated I didn&apos;t feel we needed one. We anyway discussed a few points around coffee, and I&apos;ve been able to draw a few new ideas and changes I&apos;d like to see in Gnocchi. Mainly changing the API contract to be more asynchronously so we can support &lt;a href=&quot;http://influxdb.com/&quot;&gt;InfluxDB&lt;/a&gt; more correctly, and improve Carbonara (the library we created to manipulate timeseries) based drivers to be faster.&lt;/p&gt;
&lt;p&gt;All of those should – plus a few Oslo tasks I&apos;d like to tackle – should keep me busy for the next cycle!&lt;/p&gt;
</content:encoded></item><item><title>My interview about software tests and Python</title><link>https://julien.danjou.info/blog/interview-software-tests-in-python/</link><guid isPermaLink="true">https://julien.danjou.info/blog/interview-software-tests-in-python/</guid><description>Johannes Hubertz interviewed me for his upcoming German book about Python software testing, covering my work on OpenStack and testing best practices.</description><pubDate>Mon, 11 May 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve recently been contacted by &lt;a href=&quot;http://hubertz.de/blog/&quot;&gt;Johannes Hubertz&lt;/a&gt;, who is writing a new book about Python in German called &lt;em&gt;&quot;Softwaretests mit Python&quot;&lt;/em&gt; which will be published by &lt;em&gt;Open Source Press, Munich&lt;/em&gt; this summer. His book will feature some interviews, and he was kind enough to let me write a bit about software testing. This is the interview that I gave for his book. Johannes translated to German and it will be included in Johannes&apos; book, and I decided to publish it on my blog today. Following is the original version.&lt;/p&gt;
&lt;h2&gt;How did you come to Python?&lt;/h2&gt;
&lt;p&gt;I don&apos;t recall exactly, but around ten years ago, I saw more and more people using it and decided to take a look. Back then, I was more used to Perl. I didn&apos;t really like Perl and was not getting a good grip on its object system.&lt;/p&gt;
&lt;p&gt;As soon as I found an idea to work on – if I remember correctly that was rebuildd – I started to code in Python, learning the language at the same time.&lt;/p&gt;
&lt;p&gt;I liked how Python worked, and how fast I was to able to develop and learn it, so I decided to keep using it for my next projects. I ended up diving into Python core for some reasons, even doing things like briefly hacking on projects like Cython at some point, and finally ended up working on OpenStack.&lt;/p&gt;
&lt;p&gt;OpenStack is a cloud computing platform entirely written in Python. So I&apos;ve been writing Python every day since working on it.&lt;/p&gt;
&lt;p&gt;That&apos;s what pushed me to write &lt;a href=&quot;https://thehackerguidetopython.com&quot;&gt;The Hacker&apos;s Guide to Python&lt;/a&gt; in 2013 and then self-publish it a year later in 2014, a book where I talk about doing smart and efficient Python.&lt;/p&gt;
&lt;p&gt;It had a great success, has even been translated in Chinese and Korean, so I&apos;m currently working on a second edition of the book. It has been an amazing adventure!&lt;/p&gt;
&lt;h2&gt;Zen of Python: Which line is the most important for you and why?&lt;/h2&gt;
&lt;p&gt;I like the &quot;There should be one – and preferably only one – obvious way to do it&quot;. The opposite is probably something that scared me in languages like Perl. But having one obvious way to do it is and something I tend to like in functional languages like Lisp, which are in my humble opinion, even better at that.&lt;/p&gt;
&lt;h2&gt;For a python newbie, what are the most difficult subjects in Python?&lt;/h2&gt;
&lt;p&gt;I haven&apos;t been a newbie since a while, so it&apos;s hard for me to say. I don&apos;t think the language is hard to learn. There are some subtlety in the language itself when you deeply dive into the internals, but for beginners most of the concept are pretty straight-forward. If I had to pick, in the language basics, the most difficult thing would be around the generator objects (yield).&lt;/p&gt;
&lt;p&gt;Nowadays I think the most difficult subject for new comers is what version of Python to use, which libraries to rely on, and how to package and distribute projects. Though things get better, fortunately.&lt;/p&gt;
&lt;h2&gt;When did you start using Test Driven Development and why?&lt;/h2&gt;
&lt;p&gt;I learned unit testing and TDD at school where teachers forced me to learn Java, and I hated it. The frameworks looked complicated, and I had the impression I was losing my time. Which I actually was, since I was writing disposable programs – that&apos;s the only thing you do at school.&lt;/p&gt;
&lt;p&gt;Years later, when I started to write real and bigger programs (e.g. rebuildd), I quickly ended up fixing bugs… I already fixed. That recalled me about unit tests and that it may be a good idea to start using them to stop fixing the same things over and over again.&lt;/p&gt;
&lt;p&gt;For a few years, I wrote less Python and more C code and Lua (for the &lt;a href=&quot;http://awesome.naquadah.org&quot;&gt;awesome window manager&lt;/a&gt;), and I didn&apos;t use any testing. I probably lost hundreds of hours testing manually and fixing regressions – that was a good lesson. Though I had good excuses at that time – it is/was way harder to do testing in C/Lua than in Python.&lt;/p&gt;
&lt;p&gt;Since that period, I have never stopped writing &quot;tests&quot;. When I started to hack on OpenStack, the project was adopting a &quot;no test? no merge!&quot; policy due to the high number of regressions it had during the first releases.&lt;/p&gt;
&lt;p&gt;I honestly don&apos;t think I could work on any project that does not have – at least a minimal – test coverage. It&apos;s impossible to hack efficiently on a code base that you&apos;re not able to test in just a simple command. It&apos;s also a real problem for new comers in the open source world. When there are no test, you can hack something and send a patch, and get a &quot;you broke this&quot; in response.&lt;/p&gt;
&lt;p&gt;Nowadays, this kind of response sounds unacceptable to me: if there is no test, then I didn&apos;t break anything!&lt;/p&gt;
&lt;p&gt;In the end, it&apos;s just too much frustration to work on non tested projects as I demonstrated in &lt;a href=&quot;https://julien.danjou.info/blog/python-bad-practice-concrete-case&quot;&gt;my study of whisper source code&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;What do you think to be the most often seen pitfalls of TDD and how to avoid them best?&lt;/h2&gt;
&lt;p&gt;The biggest problems are when and at what rate writing tests.&lt;/p&gt;
&lt;p&gt;On one hand, some people starts to write too precise tests way too soon. Doing that slows you down, especially when you are prototyping some idea or concept you just had. That does not mean that you should not do test at all, but you should probably start with a light coverage, until you are pretty sure that you&apos;re not going to rip every thing and start over. On the other hand, some people postpone writing tests for ever, and end up with no test all or a too thin layer of test. Which makes the project with a pretty low coverage.&lt;/p&gt;
&lt;p&gt;Basically, your test coverage should reflect the state of your project. If it&apos;s just starting, you should build a thin layer of test so you can hack it on it easily and remodel it if needed. The more your project grow, the more you should make it sold and lay more tests.&lt;/p&gt;
&lt;p&gt;Having too detailed tests is painful to make the project evolve at the start. Having not enough in a big project makes it painful to maintain it.&lt;/p&gt;
&lt;h2&gt;Do you think, TDD fits and scales well for the big projects like OpenStack?&lt;/h2&gt;
&lt;p&gt;Not only I think it fits and scales well, but I also think it&apos;s just impossible to not use TDD in such big projects.&lt;/p&gt;
&lt;p&gt;When unit and functional tests coverage was weak in OpenStack – at its beginning – it was just impossible to fix a bug or write a new feature without breaking a lot of things without even noticing. We would release version N, and a ton of old bugs present in N-2 – but fixed in N-1 – were reopened.&lt;/p&gt;
&lt;p&gt;For big projects, with a lot of different use cases, configuration options, etc, you need belt and braces. You cannot throw code in a repository thinking it&apos;s going to work ever, and you can&apos;t afford to test everything manually at each commit. That&apos;s just insane.&lt;/p&gt;
</content:encoded></item><item><title>The Hacker&apos;s Guide to Python, 2nd edition!</title><link>https://julien.danjou.info/blog/the-hacker-guide-to-python-second-edition/</link><guid isPermaLink="true">https://julien.danjou.info/blog/the-hacker-guide-to-python-second-edition/</guid><description>A year passed since the first release of The Hacker&apos;s Guide to Python in March 2014. A few hundreds copies have been distributed so far, and the feedback is wonderful!</description><pubDate>Mon, 04 May 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A year passed since the &lt;a href=&quot;https://julien.danjou.info/blog/2014/the-hacker-guide-to-python-has-been-released&quot;&gt;first release of The Hacker&apos;s Guide to Python&lt;/a&gt; in March 2014. A few hundreds copies have been distributed so far, and the feedback is wonderful!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/the-hacker-guide-to-python-darken-v2.png&quot; alt=&quot;the-hacker-guide-to-python-darken-v2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I already wrote extensively about the &lt;a href=&quot;https://julien.danjou.info/blog/making-of-the-hacker-guide-to-python&quot;&gt;making of that book&lt;/a&gt; last year, and I cannot emphasize enough how this adventure has been amazing so far. That&apos;s why I decided a few months ago to update the guide and add some new content.&lt;/p&gt;
&lt;p&gt;So let&apos;s talk about what&apos;s new in this second edition of the book!&lt;/p&gt;
&lt;p&gt;First, I obviously fixed a few things. I had some reports about small mistakes and typos which I applied as I received them. Not a lot fortunately, but it&apos;s still better to have fewer errors in a book, right?&lt;/p&gt;
&lt;p&gt;Then, I updated some of the content. Things changed since I wrote the first chapters of that guide 18 months ago. Therefore I had to rewrite some of the sections and take into account new software or libraries that were released.&lt;/p&gt;
&lt;p&gt;At last, I decided to enhance the book with one more interview. I&apos;ve requested my fellow OpenStack developer &lt;a href=&quot;https://github.com/harlowja&quot;&gt;Joshua Harlow&lt;/a&gt;, who is leading a few interesting Python projects, to join the long list of interviewees in the book. I hope you&apos;ll enjoy it!&lt;/p&gt;
&lt;p&gt;If you didn&apos;t get the book yet, go &lt;a href=&quot;https://thehackerguidetopython.com&quot;&gt;check it out&lt;/a&gt; and use the coupon &lt;strong&gt;THGTP2LAUNCH&lt;/strong&gt; to get 20% off during the next 48 hours!&lt;/p&gt;
</content:encoded></item><item><title>Gnocchi 1.0: storing metrics and resources at scale</title><link>https://julien.danjou.info/blog/openstack-gnocchi-first-release/</link><guid isPermaLink="true">https://julien.danjou.info/blog/openstack-gnocchi-first-release/</guid><description>A few months ago, I wrote a long post about what I called back then the &quot; Gnocchi experiment &quot;. Time passed and we – me and the rest of the Gnocchi team – continued to work on that project, finalizing</description><pubDate>Tue, 21 Apr 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few months ago, I wrote a long post about what I called back then the &quot;&lt;a href=&quot;https://julien.danjou.info/blog/openstack-ceilometer-the-gnocchi-experiment&quot;&gt;Gnocchi experiment&lt;/a&gt;&quot;. Time passed and we – me and the rest of the Gnocchi team – continued to work on that project, finalizing it.&lt;/p&gt;
&lt;p&gt;It&apos;s with a great pleasure that we are going to release our first &lt;em&gt;1.0&lt;/em&gt; version this month, roughly at the same time that the integrated &lt;a href=&quot;http://openstack.org&quot;&gt;OpenStack&lt;/a&gt; projects release their Kilo milestone. The &lt;a href=&quot;https://pypi.python.org/pypi/gnocchi&quot;&gt;first release candidate numbered 1.0.0rc1&lt;/a&gt; has been released this morning!&lt;/p&gt;
&lt;h2&gt;The problem to solve&lt;/h2&gt;
&lt;p&gt;Before I dive into Gnocchi details, it&apos;s important to have a good view of what problems Gnocchi is trying to solve.&lt;/p&gt;
&lt;p&gt;Most of the IT infrastructures out there consists of a set of resources. These resources have properties: some of them are simple attributes whereas others might be measurable quantities (also known as metrics).&lt;/p&gt;
&lt;p&gt;And in this context, the cloud infrastructures make no exception. We talk about instances, volumes, networks… which are all different kind of resources. The problems that are arising with the cloud trend is the scalability of storing all this data and being able to request them later, for whatever usage.&lt;/p&gt;
&lt;p&gt;What Gnocchi provides is a REST API that allows the user to manipulate resources (CRUD) and their attributes, while preserving the history of those resources and their attributes.&lt;/p&gt;
&lt;p&gt;Gnocchi is fully documented and the &lt;a href=&quot;http://gnocchi.xyz&quot;&gt;documentation is available online&lt;/a&gt;. We are the first OpenStack project to require patches to &lt;em&gt;integrate the documentation&lt;/em&gt;. We want to raise the bar, so we took a stand on that. That&apos;s part of our policy, the same way it&apos;s part of the OpenStack policy to require unit tests.&lt;/p&gt;
&lt;p&gt;I&apos;m not going to paraphrase the whole Gnocchi documentation, which covers things like installation (super easy), but I&apos;ll guide you through some basics of the features provided by the REST API. I will show you some example so you can have a better understanding of what you could leverage using Gnocchi!&lt;/p&gt;
&lt;h2&gt;Handling metrics&lt;/h2&gt;
&lt;p&gt;Gnocchi provides a full REST API to manipulate time-series that are called &lt;em&gt;metrics&lt;/em&gt;. You can easily create a metric using a simple HTTP request:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /v1/metric HTTP/1.1
Content-Type: application/json

{
  &quot;archive_policy_name&quot;: &quot;low&quot;
}

HTTP/1.1 201 Created
Location: http://localhost/v1/metric/387101dc-e4b1-4602-8f40-e7be9f0ed46a
Content-Type: application/json; charset=UTF-8

{
  &quot;archive_policy&quot;: {
    &quot;aggregation_methods&quot;: [
      &quot;std&quot;,
      &quot;sum&quot;,
      &quot;mean&quot;,
      &quot;count&quot;,
      &quot;max&quot;,
      &quot;median&quot;,
      &quot;min&quot;,
      &quot;95pct&quot;
    ],
    &quot;back_window&quot;: 0,
    &quot;definition&quot;: [
      {
        &quot;granularity&quot;: &quot;0:00:01&quot;,
        &quot;points&quot;: 3600,
        &quot;timespan&quot;: &quot;1:00:00&quot;
      },
      {
        &quot;granularity&quot;: &quot;0:30:00&quot;,
        &quot;points&quot;: 48,
        &quot;timespan&quot;: &quot;1 day, 0:00:00&quot;
      }
    ],
    &quot;name&quot;: &quot;low&quot;
  },
  &quot;created_by_project_id&quot;: &quot;e8afeeb3-4ae6-4888-96f8-2fae69d24c01&quot;,
  &quot;created_by_user_id&quot;: &quot;c10829c6-48e2-4d14-ac2b-bfba3b17216a&quot;,
  &quot;id&quot;: &quot;387101dc-e4b1-4602-8f40-e7be9f0ed46a&quot;,
  &quot;name&quot;: null,
  &quot;resource_id&quot;: null
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;archive_policy_name&lt;/code&gt; parameter defines how the measures that are being sent are going to be aggregated. You can also define archive policies using the API and specify what kind of aggregation period and granularity you want. In that case , the &lt;em&gt;low&lt;/em&gt; archive policy keeps 1 hour of data aggregated over 1 second and 1 day of data aggregated to 30 minutes. The functions used for aggregations are the mathematical functions standard deviation, minimum, maximum, … and even 95th percentile. All of that is obviously customizable and you can create your own archive policies.&lt;/p&gt;
&lt;p&gt;If you don&apos;t want to specify the archive policy manually for each metric, you can also create &lt;em&gt;archive policy rule&lt;/em&gt;, that will apply a specific archive policy based on the metric name, e.g. metrics matching &lt;code&gt;disk.*&lt;/code&gt; will be high resolution metrics so they will use the &lt;code&gt;high&lt;/code&gt; archive policy.&lt;/p&gt;
&lt;p&gt;It&apos;s also worth noting Gnocchi is precise up to the nanosecond and is not tied to the current time. You can manipulate and inject measures that are years old and precise to the nanosecond. You can also inject points with old timestamps (i.e. old compared to the most recent one in the timeseries) with an archive policy allowing it (see &lt;code&gt;back_window&lt;/code&gt; parameter).&lt;/p&gt;
&lt;p&gt;It&apos;s then possible to send measures to this metric:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /v1/metric/387101dc-e4b1-4602-8f40-e7be9f0ed46a/measures HTTP/1.1
Content-Type: application/json

[
  {
    &quot;timestamp&quot;: &quot;2014-10-06T14:33:57&quot;,
    &quot;value&quot;: 43.1
  },
  {
    &quot;timestamp&quot;: &quot;2014-10-06T14:34:12&quot;,
    &quot;value&quot;: 12
  },
  {
    &quot;timestamp&quot;: &quot;2014-10-06T14:34:20&quot;,
    &quot;value&quot;: 2
  }
  ]
  
HTTP/1.1 204 No Content
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These measures are synchronously aggregated and stored into the configured storage backend. Our most scalable storage drivers for now are either based on &lt;a href=&quot;http://launchpad.net/swift&quot;&gt;Swift&lt;/a&gt; or &lt;a href=&quot;http://ceph.com&quot;&gt;Ceph&lt;/a&gt; which are both scalable storage objects systems.&lt;/p&gt;
&lt;p&gt;It&apos;s then possible to retrieve these values:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GET /v1/metric/387101dc-e4b1-4602-8f40-e7be9f0ed46a/measures HTTP/1.1

HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8

[
  [
    &quot;2014-10-06T14:30:00.000000Z&quot;,
    1800.0,
    19.033333333333335
  ],
  [
    &quot;2014-10-06T14:33:57.000000Z&quot;,
    1.0,
    43.1
  ],
  [
    &quot;2014-10-06T14:34:12.000000Z&quot;,
    1.0,
    12.0
  ],
  [
    &quot;2014-10-06T14:34:20.000000Z&quot;,
    1.0,
    2.0
  ]
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As older Ceilometer users might notice here, metrics are only storing points and values, nothing fancy such as metadata anymore.&lt;/p&gt;
&lt;p&gt;By default, values eagerly aggregated using mean are returned for all supported granularities. You can obviously specify a time range or a different aggregation function using the &lt;code&gt;aggregation&lt;/code&gt;, &lt;code&gt;start&lt;/code&gt; and &lt;code&gt;stop&lt;/code&gt; query parameter.&lt;/p&gt;
&lt;p&gt;Gnocchi also supports doing aggregation across aggregated metrics:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GET /v1/aggregation/metric?metric=65071775-52a8-4d2e-abb3-1377c2fe5c55&amp;amp;metric=9ccdd0d6-f56a-4bba-93dc-154980b6e69a&amp;amp;start=2014-10-06T14:34&amp;amp;aggregation=mean HTTP/1.1

HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8

[
  [
    &quot;2014-10-06T14:34:12.000000Z&quot;,
    1.0,
    12.25
  ],
  [
    &quot;2014-10-06T14:34:20.000000Z&quot;,
    1.0,
    11.6
  ]
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This computes the mean of mean for the metric &lt;code&gt;65071775-52a8-4d2e-abb3-1377c2fe5c55&lt;/code&gt; and &lt;code&gt;9ccdd0d6-f56a-4bba-93dc-154980b6e69a&lt;/code&gt; starting on 6th October 2014 at 14:34 UTC.&lt;/p&gt;
&lt;h2&gt;Indexing your resources&lt;/h2&gt;
&lt;p&gt;Another object and concept that Gnocchi provides is the ability to manipulate resources. There is a basic type of resource, called &lt;em&gt;generic&lt;/em&gt;, which has very few attributes. You can extend this type to specialize it, and that&apos;s what Gnocchi does by default by providing resource types known for OpenStack such as &lt;em&gt;instance&lt;/em&gt;, &lt;em&gt;volume&lt;/em&gt;, &lt;em&gt;network&lt;/em&gt; or even &lt;em&gt;image&lt;/em&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /v1/resource/generic HTTP/1.1

Content-Type: application/json

{
  &quot;id&quot;: &quot;75C44741-CC60-4033-804E-2D3098C7D2E9&quot;,
  &quot;project_id&quot;: &quot;BD3A1E52-1C62-44CB-BF04-660BD88CD74D&quot;,
  &quot;user_id&quot;: &quot;BD3A1E52-1C62-44CB-BF04-660BD88CD74D&quot;
}

HTTP/1.1 201 Created
Location: http://localhost/v1/resource/generic/75c44741-cc60-4033-804e-2d3098c7d2e9
ETag: &quot;e3acd0681d73d85bfb8d180a7ecac75fce45a0dd&quot;
Last-Modified: Fri, 17 Apr 2015 11:18:48 GMT
Content-Type: application/json; charset=UTF-8

{
  &quot;created_by_project_id&quot;: &quot;ec181da1-25dd-4a55-aa18-109b19e7df3a&quot;,
  &quot;created_by_user_id&quot;: &quot;4543aa2a-6ebf-4edd-9ee0-f81abe6bb742&quot;,
  &quot;ended_at&quot;: null,
  &quot;id&quot;: &quot;75c44741-cc60-4033-804e-2d3098c7d2e9&quot;,
  &quot;metrics&quot;: {},
  &quot;project_id&quot;: &quot;bd3a1e52-1c62-44cb-bf04-660bd88cd74d&quot;,
  &quot;revision_end&quot;: null,
  &quot;revision_start&quot;: &quot;2015-04-17T11:18:48.696288Z&quot;,
  &quot;started_at&quot;: &quot;2015-04-17T11:18:48.696275Z&quot;,
  &quot;type&quot;: &quot;generic&quot;,
  &quot;user_id&quot;: &quot;bd3a1e52-1c62-44cb-bf04-660bd88cd74d&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The resource is created with the UUID provided by the user. Gnocchi handles the history of the resource, and that&apos;s what the &lt;code&gt;revision_start&lt;/code&gt; and &lt;code&gt;revision_end&lt;/code&gt; fields are for. They indicates the lifetime of this revision of the resource. The &lt;code&gt;ETag&lt;/code&gt; and &lt;code&gt;Last-Modified&lt;/code&gt; headers are also unique to this resource revision and can be used in a subsequent request using &lt;code&gt;If-Match&lt;/code&gt; or &lt;code&gt;If-Not-Match&lt;/code&gt; header, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GET /v1/resource/generic/75c44741-cc60-4033-804e-2d3098c7d2e9 HTTP/1.1
If-Not-Match: &quot;e3acd0681d73d85bfb8d180a7ecac75fce45a0dd&quot;

HTTP/1.1 304 Not Modified
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which is useful to synchronize and update any view of the resources you might have in your application.&lt;/p&gt;
&lt;p&gt;You can use the &lt;code&gt;PATCH&lt;/code&gt; HTTP method to modify properties of the resource, which will create a new revision of the resource. The history of the resources are available via the REST API obviously.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;metrics&lt;/code&gt; properties of the resource allow you to link metrics to a resource. You can link existing metrics or create new ones dynamically:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /v1/resource/generic HTTP/1.1
Content-Type: application/json

{
  &quot;id&quot;: &quot;AB68DA77-FA82-4E67-ABA9-270C5A98CBCB&quot;,
  &quot;metrics&quot;: {
    &quot;temperature&quot;: {
      &quot;archive_policy_name&quot;: &quot;low&quot;
    }
  },
  &quot;project_id&quot;: &quot;BD3A1E52-1C62-44CB-BF04-660BD88CD74D&quot;,
  &quot;user_id&quot;: &quot;BD3A1E52-1C62-44CB-BF04-660BD88CD74D&quot;
}

HTTP/1.1 201 Created
Location: http://localhost/v1/resource/generic/ab68da77-fa82-4e67-aba9-270c5a98cbcb
ETag: &quot;9f64c8890989565514eb50c5517ff01816d12ff6&quot;
Last-Modified: Fri, 17 Apr 2015 14:39:22 GMT
Content-Type: application/json; charset=UTF-8

{
  &quot;created_by_project_id&quot;: &quot;cfa2ebb5-bbf9-448f-8b65-2087fbecf6ad&quot;,
  &quot;created_by_user_id&quot;: &quot;6aadfc0a-da22-4e69-b614-4e1699d9e8eb&quot;,
  &quot;ended_at&quot;: null,
  &quot;id&quot;: &quot;ab68da77-fa82-4e67-aba9-270c5a98cbcb&quot;,
  &quot;metrics&quot;: {
    &quot;temperature&quot;: &quot;ad53cf29-6d23-48c5-87c1-f3bf5e8bb4a0&quot;
  },
  &quot;project_id&quot;: &quot;bd3a1e52-1c62-44cb-bf04-660bd88cd74d&quot;,
  &quot;revision_end&quot;: null,
  &quot;revision_start&quot;: &quot;2015-04-17T14:39:22.181615Z&quot;,
  &quot;started_at&quot;: &quot;2015-04-17T14:39:22.181601Z&quot;,
  &quot;type&quot;: &quot;generic&quot;,
  &quot;user_id&quot;: &quot;bd3a1e52-1c62-44cb-bf04-660bd88cd74d&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Haystack, needle? Find!&lt;/h2&gt;
&lt;p&gt;With such a system, it becomes very easy to index all your resources, meter them and retrieve this data. What&apos;s even more interesting is to query the system to find and list the resources you are interested in!&lt;/p&gt;
&lt;p&gt;You can search for a resource based on any field, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /v1/search/resource/instance HTTP/1.1
Content-Type: application/json

{
  &quot;=&quot;: {
    &quot;user_id&quot;: &quot;bd3a1e52-1c62-44cb-bf04-660bd88cd74d&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That query will return a list of all resources owned by the &lt;code&gt;user_id&lt;/code&gt; &lt;code&gt;bd3a1e52-1c62-44cb-bf04-660bd88cd74d&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You can do fancier queries such as retrieving all the instances started by a user this month:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /v1/search/resource/instance HTTP/1.1
Content-Type: application/json
Content-Length: 113

{
  &quot;and&quot;: [
    {
      &quot;=&quot;: {
        &quot;user_id&quot;: &quot;bd3a1e52-1c62-44cb-bf04-660bd88cd74d&quot;
      }
    },
    {
      &quot;&amp;gt;=&quot;: {
        &quot;started_at&quot;: &quot;2015-04-01&quot;
      }
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And you can even do fancier queries than the fancier ones (still following?). What if we wanted to retrieve all the instances that were on host &lt;code&gt;foobar&lt;/code&gt; the 15th April and who had already 30 minutes of uptime? Let&apos;s ask Gnocchi to look in the history!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /v1/search/resource/instance?history=true HTTP/1.1
Content-Type: application/json
Content-Length: 113

{
  &quot;and&quot;: [
    {
      &quot;=&quot;: {
        &quot;host&quot;: &quot;foobar&quot;
      }
    },
    {
      &quot;&amp;gt;=&quot;: {
        &quot;lifespan&quot;: &quot;1 hour&quot;
      }
    },
    {
      &quot;&amp;lt;=&quot;: {
        &quot;revision_start&quot;: &quot;2015-04-15&quot;
      }
    }

  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I could also mention the fact that you can &lt;a href=&quot;http://docs.openstack.org/developer/gnocchi/rest.html#searching-for-values-in-metrics&quot;&gt;search for value in metrics&lt;/a&gt;.&lt;br /&gt;
One feature that I will very likely include in Gnocchi 1.1 is the ability to search for resource whose specific metrics matches some value. For example, having the ability to search for instances whose CPU consumption was over 80% during a month.&lt;/p&gt;
&lt;h2&gt;Cherries on the cake&lt;/h2&gt;
&lt;p&gt;While Gnocchi is well integrated and based on common OpenStack technology, please do note that it is completely able to function without any other OpenStack component and is pretty straight-forward to deploy.&lt;/p&gt;
&lt;p&gt;Gnocchi also implements a full RBAC system based on the &lt;a href=&quot;http://docs.openstack.org/developer/oslo.policy/&quot;&gt;OpenStack standard oslo.policy&lt;/a&gt; and which allows pretty fine grained control of permissions.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-resource-html.png&quot; alt=&quot;gnocchi-resource-html&quot; /&gt;&lt;/p&gt;
&lt;p&gt;There is also some work ongoing to have HTML rendering when browsing the API using a Web browser. While still simple, we&apos;d like to have a minimal Web interface served on top of the API for the same price!&lt;/p&gt;
&lt;p&gt;Ceilometer alarm subsystem supports Gnocchi with the Kilo release, meaning you can use it to trigger actions when a metric value crosses some threshold. And OpenStack &lt;a href=&quot;http://launchpad.net/heat&quot;&gt;Heat&lt;/a&gt; also supports auto-scaling your instances based on Ceilometer+Gnocchi alarms.&lt;/p&gt;
&lt;p&gt;And there are a few more API calls that I didn&apos;t talk about here, so don&apos;t hesitate to take a peek at the &lt;a href=&quot;http://gnocchi.xyz&quot;&gt;full documentation&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;Towards Gnocchi 1.1!&lt;/h2&gt;
&lt;p&gt;Gnocchi is a different beast in the OpenStack community. It is under the umbrella of the Ceilometer program, but it&apos;s one of the first projects that is not part of the (old) integrated release. Therefore we decided to have a release schedule not directly linked to the OpenStack and we&apos;ll release more often that the rest of the old OpenStack components – probably once every 2 months or the like.&lt;/p&gt;
&lt;p&gt;What&apos;s coming next is a close integration with Ceilometer (e.g. moving the dispatcher code from Gnocchi to Ceilometer) and probably more features as we have more requests from our users. We are also exploring different backends such as InfluxDB (storage) or MongoDB (indexer).&lt;/p&gt;
&lt;p&gt;Stay tuned, and happy hacking!&lt;/p&gt;
</content:encoded></item><item><title>Hacking Python AST: checking methods declaration</title><link>https://julien.danjou.info/blog/python-ast-checking-method-declaration/</link><guid isPermaLink="true">https://julien.danjou.info/blog/python-ast-checking-method-declaration/</guid><description>A few months ago, I wrote the definitive guide about Python method declaration, which had quite a good success.</description><pubDate>Mon, 16 Feb 2015 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few months ago, I wrote &lt;a href=&quot;https://julien.danjou.info/blog/guide-python-static-class-abstract-methods&quot;&gt;the definitive guide about Python method declaration&lt;/a&gt;, which had quite a good success. I still fight every day in &lt;a href=&quot;http://openstack.org&quot;&gt;OpenStack&lt;/a&gt; to have the developers declare their methods correctly in the patches they submit.&lt;/p&gt;
&lt;h2&gt;Automation plan&lt;/h2&gt;
&lt;p&gt;The thing is, I really dislike doing the same things over and over again. Furthermore, I&apos;m not perfect either, and I miss a lot of these kind of problems in the reviews I made. So I decided to replace me by a program – a more scalable and less error-prone version of my brain.&lt;/p&gt;
&lt;p&gt;In OpenStack, we rely on &lt;em&gt;&lt;a href=&quot;http://flake8.readthedocs.org/en/2.2.3/&quot;&gt;flake8&lt;/a&gt;&lt;/em&gt; to do static analysis of our Python code in order to spot common programming mistakes.&lt;/p&gt;
&lt;p&gt;But we are really pedantic, so we wrote some extra hacking rules that we enforce on our code. To that end, we wrote a &lt;em&gt;flake8&lt;/em&gt; extension called &lt;a href=&quot;https://pypi.python.org/pypi/hacking&quot;&gt;hacking&lt;/a&gt;. I really like these rules, I even recommend to apply them in your own project. Though I might be biased or victim of Stockholm syndrome. Your call.&lt;/p&gt;
&lt;p&gt;Anyway, it&apos;s pretty clear that I need to add a check for method declaration in &lt;em&gt;hacking&lt;/em&gt;. Let&apos;s write a &lt;em&gt;flake8&lt;/em&gt; extension!&lt;/p&gt;
&lt;h2&gt;Typical error&lt;/h2&gt;
&lt;p&gt;The typical error I spot is the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Foo(object):
    # self is not used, the method does not need
    # to be bound, it should be declared static
    def bar(self, a, b, c):
        return a + b - c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That would be the correct version:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Foo(object):
    @staticmethod
    def bar(a, b, c):
        return a + b - c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This kind of mistake is not a show-stopper. It&apos;s just not optimized. Why you have to manually declare static or class methods might be a language issue, but I don&apos;t want to debate about Python misfeatures or design flaws.&lt;/p&gt;
&lt;h2&gt;Strategy&lt;/h2&gt;
&lt;p&gt;We could probably use some big magical regular expression to catch this problem. &lt;em&gt;flake8&lt;/em&gt; is based on the &lt;em&gt;&lt;a href=&quot;https://pypi.python.org/pypi/pep8&quot;&gt;pep8&lt;/a&gt;&lt;/em&gt; tool, which can do a line by line analysis of the code. But this method would make it very hard and error prone to detect this pattern.&lt;/p&gt;
&lt;p&gt;Though it&apos;s also possible to do an AST based analysis on on a per-file basis with &lt;em&gt;pep8&lt;/em&gt;. So that&apos;s the method I pick as it&apos;s the most solid.&lt;/p&gt;
&lt;h2&gt;AST analysis&lt;/h2&gt;
&lt;p&gt;I won&apos;t dive deeply into Python AST and how it works. You can find plenty of sources on the Internet, and I even talk about it a bit in my book &lt;em&gt;&lt;a href=&quot;https://thehackerguidetopython.com&quot;&gt;The Hacker&apos;s Guide to Python&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/python-ast.png&quot; alt=&quot;python-ast&quot; /&gt;&lt;/p&gt;
&lt;p&gt;To check correctly if all the methods in a Python file are correctly declared, we need to do the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Iterate over all the statement node of the AST&lt;/li&gt;
&lt;li&gt;Check that the statement is a class definition (&lt;code&gt;ast.ClassDef&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Iterate over all the function definitions (&lt;code&gt;ast.FunctionDef&lt;/code&gt;) of that class statement to check if it is already declared with &lt;code&gt;@staticmethod&lt;/code&gt; or not&lt;/li&gt;
&lt;li&gt;If the method is not declared static, we need to check if the first argument (&lt;code&gt;self&lt;/code&gt;) is used somewhere in the method&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;&lt;em&gt;Flake8&lt;/em&gt; plugin&lt;/h2&gt;
&lt;p&gt;In order to register a new plugin in &lt;em&gt;flake8&lt;/em&gt; via &lt;em&gt;hacking&lt;/em&gt;, we just need to add an entry in &lt;code&gt;setup.cfg&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[entry_points]
flake8.extension =
    […]
    H904 = hacking.checks.other:StaticmethodChecker
    H905 = hacking.checks.other:StaticmethodChecker
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We register 2 &lt;em&gt;hacking&lt;/em&gt; codes here. As you will notice later, we are actually going to add an extra check in our code for the same price. Stay tuned.&lt;/p&gt;
&lt;p&gt;The next step is to write the actual plugin. Since we are using an AST based check, the plugin needs to be a class following a certain signature:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@core.flake8ext
class StaticmethodChecker(object):
    def __init__(self, tree, filename):
        self.tree = tree

    def run(self):
        pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So far, so good and pretty easy. We store the tree locally, then we just need to use it in &lt;code&gt;run()&lt;/code&gt; and &lt;code&gt;yield&lt;/code&gt; the problem we discover following &lt;code&gt;pep8&lt;/code&gt; expected signature, which is a tuple of &lt;code&gt;(lineno, col_offset, error_string, code)&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;This AST is made for walking ♪ ♬ ♩&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;ast&lt;/code&gt; module provides the &lt;code&gt;walk&lt;/code&gt; function, that allow to iterate easily on a tree. We&apos;ll use that to run through the AST. First, let&apos;s write a loop that ignores the statement that are not class definition.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@core.flake8ext
class StaticmethodChecker(object):
    def __init__(self, tree, filename):
        self.tree = tree

    def run(self):
        for stmt in ast.walk(self.tree):
            # Ignore non-class
            if not isinstance(stmt, ast.ClassDef):
                continue
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We still don&apos;t check for anything, but we know how to ignore statement that are not class definitions. The next step need to be to ignore what is not function definition. We just iterate over the attributes of the class definition.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for stmt in ast.walk(self.tree):
    # Ignore non-class
    if not isinstance(stmt, ast.ClassDef):
        continue
    # If it&apos;s a class, iterate over its body member to find methods
    for body_item in stmt.body:
        # Not a method, skip
        if not isinstance(body_item, ast.FunctionDef):
            continue
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&apos;re all set for checking the method, which is &lt;code&gt;body_item&lt;/code&gt;. First, we need to check if it&apos;s already declared as static. If so, we don&apos;t have to do any further check and we can bail out.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for stmt in ast.walk(self.tree):
    # Ignore non-class
    if not isinstance(stmt, ast.ClassDef):
        continue
    # If it&apos;s a class, iterate over its body member to find methods
    for body_item in stmt.body:
        # Not a method, skip
        if not isinstance(body_item, ast.FunctionDef):
            continue
        # Check that it has a decorator
        for decorator in body_item.decorator_list:
            if (isinstance(decorator, ast.Name)
               and decorator.id == &apos;staticmethod&apos;):
                # It&apos;s a static function, it&apos;s OK
                break
        else:
            # Function is not static, we do nothing for now
            pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that we use the special &lt;code&gt;for/else&lt;/code&gt; form of Python, where the &lt;code&gt;else&lt;/code&gt; is evaluated unless we used &lt;code&gt;break&lt;/code&gt; to exit the &lt;code&gt;for&lt;/code&gt; loop.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for stmt in ast.walk(self.tree):
    # Ignore non-class
    if not isinstance(stmt, ast.ClassDef):
        continue
    # If it&apos;s a class, iterate over its body member to find methods
    for body_item in stmt.body:
        # Not a method, skip
        if not isinstance(body_item, ast.FunctionDef):
            continue
        # Check that it has a decorator
        for decorator in body_item.decorator_list:
            if (isinstance(decorator, ast.Name)
               and decorator.id == &apos;staticmethod&apos;):
                # It&apos;s a static function, it&apos;s OK
                break
        else:
            try:
                first_arg = body_item.args.args[0]
            except IndexError:
                yield (
                    body_item.lineno,
                    body_item.col_offset,
                    &quot;H905: method misses first argument&quot;,
                    &quot;H905&quot;,
                )
                # Check next method
                continue
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We finally added some check! We grab the first argument from the method signature. Unless it fails, and in that case, we know there&apos;s a problem: you can&apos;t have a bound method without the &lt;code&gt;self&lt;/code&gt; argument, therefore we raise the &lt;code&gt;H905&lt;/code&gt; code to signal a method that misses its first argument.&lt;/p&gt;
&lt;p&gt;Now you know why we registered this second &lt;code&gt;pep8&lt;/code&gt; code along with &lt;code&gt;H904&lt;/code&gt; in &lt;code&gt;setup.cfg&lt;/code&gt;. We have here a good opportunity to kill two birds with one stone.&lt;/p&gt;
&lt;p&gt;The next step is to check if that first argument is used in the code of the method.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for stmt in ast.walk(self.tree):
    # Ignore non-class
    if not isinstance(stmt, ast.ClassDef):
        continue
    # If it&apos;s a class, iterate over its body member to find methods
    for body_item in stmt.body:
        # Not a method, skip
        if not isinstance(body_item, ast.FunctionDef):
            continue
        # Check that it has a decorator
        for decorator in body_item.decorator_list:
            if (isinstance(decorator, ast.Name)
               and decorator.id == &apos;staticmethod&apos;):
                # It&apos;s a static function, it&apos;s OK
                break
        else:
            try:
                first_arg = body_item.args.args[0]
            except IndexError:
                yield (
                    body_item.lineno,
                    body_item.col_offset,
                    &quot;H905: method misses first argument&quot;,
                    &quot;H905&quot;,
                )
                # Check next method
                continue
            for func_stmt in ast.walk(body_item):
                if six.PY3:
                    if (isinstance(func_stmt, ast.Name)
                       and first_arg.arg == func_stmt.id):
                        # The first argument is used, it&apos;s OK
                        break
                else:
                    if (func_stmt != first_arg
                       and isinstance(func_stmt, ast.Name)
                       and func_stmt.id == first_arg.id):
                        # The first argument is used, it&apos;s OK
                        break
            else:
                yield (
                    body_item.lineno,
                    body_item.col_offset,
                    &quot;H904: method should be declared static&quot;,
                    &quot;H904&quot;,
                )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To that end, we iterate using &lt;code&gt;ast.walk&lt;/code&gt; again and we look for the use of the same variable named (usually &lt;code&gt;self&lt;/code&gt;, but if could be anything, like &lt;code&gt;cls&lt;/code&gt; for &lt;code&gt;@classmethod&lt;/code&gt;) in the body of the function. If not found, we finally yield the &lt;code&gt;H904&lt;/code&gt; error code. Otherwise, we&apos;re good.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I&apos;ve &lt;a href=&quot;https://review.openstack.org/#/c/151952/&quot;&gt;submitted this patch to &lt;em&gt;hacking&lt;/em&gt;&lt;/a&gt;, and, fingers crossed, it might be merged one day. If it&apos;s not I&apos;ll create a new Python package with that check for flake8. The actual submitted code is a bit more complex to take into account the use of &lt;a href=&quot;https://docs.python.org/2/library/abc.html&quot;&gt;&lt;code&gt;abc&lt;/code&gt;&lt;/a&gt; module and include some tests.&lt;/p&gt;
&lt;p&gt;As you may have notice, the code walks over the module AST definition several times. There might be a couple of optimization to browse the AST in only one pass, but I&apos;m not sure it&apos;s worth it considering the actual usage of the tool. I&apos;ll let that as an exercise for the reader interested in contributing to OpenStack. 😉&lt;/p&gt;
&lt;p&gt;Happy hacking!&lt;/p&gt;
</content:encoded></item><item><title>Distributed group management and locking in Python with tooz</title><link>https://julien.danjou.info/blog/python-distributed-membership-lock-with-tooz/</link><guid isPermaLink="true">https://julien.danjou.info/blog/python-distributed-membership-lock-with-tooz/</guid><description>With OpenStack embracing the Tooz library more and more over the past year, I think it&apos;s a good start to write a bit about it.</description><pubDate>Fri, 21 Nov 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;With &lt;a href=&quot;http://openstack.org&quot;&gt;OpenStack&lt;/a&gt; embracing the &lt;a href=&quot;http://launchpad.net/python-tooz&quot;&gt;Tooz&lt;/a&gt; library more and more over the past year, I think it&apos;s a good start to write a bit about it.&lt;/p&gt;
&lt;h2&gt;A bit of history&lt;/h2&gt;
&lt;p&gt;A little more than year ago, with my colleague Yassine Lamgarchal and others at &lt;a href=&quot;http://enovance.com&quot;&gt;eNovance&lt;/a&gt;, we investigated on how to solve a problem often encountered inside OpenStack: synchronization of multiple distributed workers. And while many people in our ecosystem continue to drive development by adding new bells and whistles, we made a point of solving new problems with a generic solution able to address the technical debt at the same time.&lt;/p&gt;
&lt;p&gt;Yassine wrote the first ideas of what should be the &lt;a href=&quot;https://wiki.openstack.org/wiki/Oslo/blueprints/service-sync&quot;&gt;group membership service&lt;/a&gt; that was needed for OpenStack, identifying several projects that could make use of this. I&apos;ve presented this concept during the &lt;a href=&quot;https://www.openstack.org/summit/openstack-summit-hong-kong-2013/&quot;&gt;OpenStack Summit in Hong-Kong&lt;/a&gt; during an Oslo session. It turned out that the idea was well-received, and the week following the summit we started the &lt;a href=&quot;http://launchpad.net/python-tooz&quot;&gt;tooz&lt;/a&gt; project on &lt;a href=&quot;http://ci.openstack.org/stackforge.html&quot;&gt;StackForge&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Goals&lt;/h2&gt;
&lt;p&gt;Tooz is a Python library that provides a coordination API. Its primary goal is to handle groups and membership of these groups in distributed systems.&lt;/p&gt;
&lt;p&gt;Tooz also provides another useful feature which is distributed locking. This allows distributed nodes to acquire and release locks in order to synchronize themselves (for example to access a shared resource).&lt;/p&gt;
&lt;h2&gt;The architecture&lt;/h2&gt;
&lt;p&gt;If you are familiar with distributed systems, you might be thinking that there are a lot of solutions already available to solve these issues: &lt;a href=&quot;http://zookeeper.apache.org/&quot;&gt;ZooKeeper&lt;/a&gt;, the &lt;a href=&quot;http://raftconsensus.github.io/&quot;&gt;Raft consensus algorithm&lt;/a&gt; or even &lt;a href=&quot;http://redis.io/&quot;&gt;Redis&lt;/a&gt; for example.&lt;/p&gt;
&lt;p&gt;You&apos;ll be thrilled to learn that Tooz is not the result of the &lt;a href=&quot;http://en.wikipedia.org/wiki/Not_invented_here&quot;&gt;NIH&lt;/a&gt; syndrome, but is an abstraction layer on top of all these solutions. It uses drivers to provide the real functionalities behind, and does not try to do anything fancy.&lt;/p&gt;
&lt;p&gt;All the drivers do not have the same amount of functionality of robustness, but depending on your environment, any available driver might be suffice. Like most of OpenStack, we let the deployers/operators/developers chose whichever backend they want to use, informing them of the potential trade-offs they will make.&lt;/p&gt;
&lt;p&gt;So far, Tooz provides drivers based on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://pypi.python.org/pypi/kazoo&quot;&gt;Kazoo&lt;/a&gt; (ZooKeeper)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pypi.python.org/pypi/zake&quot;&gt;Zake&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://memcached.org&quot;&gt;memcached&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://redis.io&quot;&gt;redis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.tldp.org/LDP/lpg/node21.html&quot;&gt;SysV IPC&lt;/a&gt; (only for distributed locks for now)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://postgresql.org&quot;&gt;PostgreSQL&lt;/a&gt; (only for distributed locks for now)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://mysql.org&quot;&gt;MySQL&lt;/a&gt; (only for distributed locks for now)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All drivers are distributed across processes. Some can be distributed across the network (ZooKeeper, memcached, redis…) and some are only available on the same host (IPC).&lt;/p&gt;
&lt;p&gt;Also note that the Tooz API is completely asynchronous, allowing it to be more efficient, and potentially included in an event loop.&lt;/p&gt;
&lt;h2&gt;Features&lt;/h2&gt;
&lt;h3&gt;Group membership&lt;/h3&gt;
&lt;p&gt;Tooz provides an API to manage group membership. The basic operations provided are: the creation of a group, the ability to join it, leave it and list its members. It&apos;s also possible to be notified as soon as a member joins or leaves a group.&lt;/p&gt;
&lt;h3&gt;Leader election&lt;/h3&gt;
&lt;p&gt;Each group can have a leader elected. Each member can decide if it wants to run for the election. If the leader disappears, another one is elected from the list of current candidates. It&apos;s possible to be notified of the election result and to retrieve the leader of a group at any moment.&lt;/p&gt;
&lt;h3&gt;Distributed locking&lt;/h3&gt;
&lt;p&gt;When trying to synchronize several workers in a distributed environment, you may need a way to lock access to some resources. That&apos;s what a distributed lock can help you with.&lt;/p&gt;
&lt;h2&gt;Adoption in OpenStack&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://launchpad.net/ceilometer&quot;&gt;Ceilometer&lt;/a&gt; is the first project in OpenStack to use Tooz. It has replaced part of the old alarm distribution system, where RPC was used to detect active alarm evaluator workers. The group membership feature of Tooz was leveraged by Ceilometer to coordinate between alarm evaluator workers.&lt;/p&gt;
&lt;p&gt;Another new feature part of the Juno release of Ceilometer is the distribution of polling tasks of the central agent among multiple workers. There&apos;s again a group membership issue to know which nodes are online and available to receive polling tasks, so Tooz is also being used here.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;http://wiki.openstack.org/Oslo&quot;&gt;Oslo&lt;/a&gt; team &lt;a href=&quot;https://review.openstack.org/#/c/122439/&quot;&gt;has accepted the adoption of Tooz&lt;/a&gt; during this release cycle. That means that it will be maintained by more developers, and will be part of the OpenStack release process.&lt;/p&gt;
&lt;p&gt;This opens the door to push Tooz further in OpenStack. Our next candidate would be write a service group driver for &lt;a href=&quot;http://launchpad.net/nova&quot;&gt;Nova&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;http://tooz.rtfd.org/&quot;&gt;complete documentation for Tooz is available online&lt;/a&gt; and has examples for the various features described here, go read it if you&apos;re curious and adventurous!&lt;/p&gt;
</content:encoded></item><item><title>Python bad practice, a concrete case</title><link>https://julien.danjou.info/blog/python-bad-practice-concrete-case/</link><guid isPermaLink="true">https://julien.danjou.info/blog/python-bad-practice-concrete-case/</guid><description>A lot of people read up on good Python practice, and there&apos;s plenty of information about that on the Internet. Many tips are included in the book I wrote this year, The Hacker&apos;s Guide to Python.</description><pubDate>Mon, 15 Sep 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A lot of people read up on good Python practice, and there&apos;s plenty of information about that on the Internet. Many tips are included in the book I wrote this year, &lt;a href=&quot;https://thehackerguidetopython.com&quot;&gt;The Hacker&apos;s Guide to Python&lt;/a&gt;. Today I&apos;d like to show a concrete case of code that I don&apos;t consider being the state of the art.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/python-thumb-down.png&quot; alt=&quot;python-thumb-down&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In my &lt;a href=&quot;https://julien.danjou.info/blog/openstack-ceilometer-the-gnocchi-experiment&quot;&gt;last article&lt;/a&gt; where I talked about my new project Gnocchi, I wrote about how I tested, hacked and then ditched &lt;em&gt;&lt;a href=&quot;http://graphite.wikidot.com/whisper&quot;&gt;whisper&lt;/a&gt;&lt;/em&gt; out. Here I&apos;m going to explain part of my thought process and a few things that raised my eyebrows when hacking this code.&lt;/p&gt;
&lt;p&gt;Before I start, please don&apos;t get the spirit of this article wrong. It&apos;s in no way a personal attack to the authors and contributors (who I don&apos;t know). Furthermore, &lt;em&gt;whisper&lt;/em&gt; is a piece of code that is in production in thousands of installation, storing metrics for years. While I can argue that I consider the code not to be following best practice, it definitely works well enough and is worthy to a lot of people.&lt;/p&gt;
&lt;h2&gt;Tests&lt;/h2&gt;
&lt;p&gt;The first thing that I noticed when trying to hack on &lt;em&gt;whisper&lt;/em&gt;, is the lack of test. There&apos;s only one file containing tests, named &lt;code&gt;test_whisper.py&lt;/code&gt;, and the coverage it provides is pretty low. One can check that using the &lt;em&gt;coverage&lt;/em&gt; tool.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ coverage run test_whisper.py
...........
----------------------------------------------------------------------
Ran 11 tests in 0.014s

OK
$ coverage report
Name           Stmts   Miss  Cover
----------------------------------
test_whisper     134      4    97%
whisper          584    227    61%
----------------------------------
TOTAL            718    231    67%
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While one would think that 61% is &quot;not so bad&quot;, taking a quick peak at the actual test code shows that the tests are incomplete. Why I mean by incomplete is that they for example use the library to store values into a database, but they never check if the results can be fetched and if the fetched results are accurate. Here&apos;s a good reason one should never blindly trust the test cover percentage as a quality metric.&lt;/p&gt;
&lt;p&gt;When I tried to modify &lt;em&gt;whisper&lt;/em&gt;, as the tests do not check the entire cycle of the values fed into the database, I ended up doing wrong changes but had the tests still pass.&lt;/p&gt;
&lt;h2&gt;No PEP 8, no Python 3&lt;/h2&gt;
&lt;p&gt;The code doesn&apos;t respect PEP 8 . A run of &lt;a href=&quot;https://flake8.readthedocs.org/&quot;&gt;flake8&lt;/a&gt; + &lt;a href=&quot;https://pypi.python.org/pypi/hacking&quot;&gt;hacking&lt;/a&gt; shows 732 errors… While it does not impact the code itself, it&apos;s more painful to hack on it than it is on most Python projects.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;hacking&lt;/em&gt; tool also shows that the code is not Python 3 ready as there is usage of Python 2 only syntax.&lt;/p&gt;
&lt;p&gt;A good way to fix that would be to set up &lt;a href=&quot;https://testrun.org/tox/latest/&quot;&gt;tox&lt;/a&gt; and adds a few targets for PEP 8 checks and Python 3 tests. Even if the test suite is not complete, starting by having flake8 run without errors and the few unit tests working with Python 3 should put the project in a better light.&lt;/p&gt;
&lt;h2&gt;Not using idiomatic Python&lt;/h2&gt;
&lt;p&gt;A lot of the code could be simplified by using idiomatic Python. Let&apos;s take a simple example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def fetch(path,fromTime,untilTime=None,now=None):
  fh = None
  try:
    fh = open(path,&apos;rb&apos;)
    return file_fetch(fh, fromTime, untilTime, now)
  finally:
    if fh:
      fh.close()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That piece of code could be easily rewritten as:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def fetch(path,fromTime,untilTime=None,now=None):
  with open(path, &apos;rb&apos;) as fh:
    return file_fetch(fh, fromTime, untilTime, now)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, the function looks actually so simple that one can even wonder why it should exists – but why not.&lt;/p&gt;
&lt;p&gt;Usage of loops could also be made more Pythonic:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for i,archive in enumerate(archiveList):
  if i == len(archiveList) - 1:
    break
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;could be actually:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for archive in itertools.islice(archiveList, len(archiveList) - 1):
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That reduce the code size and makes it easier to read through the code.&lt;/p&gt;
&lt;h2&gt;Wrong abstraction level&lt;/h2&gt;
&lt;p&gt;Also, one thing that I noticed in &lt;em&gt;whisper&lt;/em&gt;, is that it abstracts its features at the wrong level.&lt;/p&gt;
&lt;p&gt;Take the &lt;code&gt;create()&lt;/code&gt; function, it&apos;s pretty obvious:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def create(path,archiveList,xFilesFactor=None,aggregationMethod=None,sparse=False,useFallocate=False):
  # Set default params
  if xFilesFactor is None:
    xFilesFactor = 0.5
  if aggregationMethod is None:
    aggregationMethod = &apos;average&apos;

  #Validate archive configurations...
  validateArchiveList(archiveList)

  #Looks good, now we create the file and write the header
  if os.path.exists(path):
    raise InvalidConfiguration(&quot;File %s already exists!&quot; % path)
  fh = None
  try:
    fh = open(path,&apos;wb&apos;)
    if LOCK:
      fcntl.flock( fh.fileno(), fcntl.LOCK_EX )

    aggregationType = struct.pack( longFormat, aggregationMethodToType.get(aggregationMethod, 1) )
    oldest = max([secondsPerPoint * points for secondsPerPoint,points in archiveList])
    maxRetention = struct.pack( longFormat, oldest )
    xFilesFactor = struct.pack( floatFormat, float(xFilesFactor) )
    archiveCount = struct.pack(longFormat, len(archiveList))
    packedMetadata = aggregationType + maxRetention + xFilesFactor + archiveCount
    fh.write(packedMetadata)
    headerSize = metadataSize + (archiveInfoSize * len(archiveList))
    archiveOffsetPointer = headerSize

    for secondsPerPoint,points in archiveList:
      archiveInfo = struct.pack(archiveInfoFormat, archiveOffsetPointer, secondsPerPoint, points)
      fh.write(archiveInfo)
      archiveOffsetPointer += (points * pointSize)

    #If configured to use fallocate and capable of fallocate use that, else
    #attempt sparse if configure or zero pre-allocate if sparse isn&apos;t configured.
    if CAN_FALLOCATE and useFallocate:
      remaining = archiveOffsetPointer - headerSize
      fallocate(fh, headerSize, remaining)
    elif sparse:
      fh.seek(archiveOffsetPointer - 1)
      fh.write(&apos;\x00&apos;)
    else:
      remaining = archiveOffsetPointer - headerSize
      chunksize = 16384
      zeroes = &apos;\x00&apos; * chunksize
      while remaining &amp;gt; chunksize:
        fh.write(zeroes)
        remaining -= chunksize
      fh.write(zeroes[:remaining])

    if AUTOFLUSH:
      fh.flush()
      os.fsync(fh.fileno())
  finally:
    if fh:
      fh.close()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The function is doing &lt;strong&gt;everything&lt;/strong&gt;: checking if the file doesn&apos;t exist already, opening it, building the structured data, writing this, building more structure, then writing that, etc.&lt;/p&gt;
&lt;p&gt;That means that the caller has to give a file path, even if it just wants a &lt;em&gt;whipser&lt;/em&gt; data structure to store itself elsewhere. &lt;code&gt;StringIO()&lt;/code&gt; could be used to fake a file handler, but it will fail if the call to &lt;code&gt;fcntl.flock()&lt;/code&gt; is not disabled – and it is inefficient anyway.&lt;/p&gt;
&lt;p&gt;There&apos;s a lot of other functions in the code, such as for example &lt;code&gt;setAggregationMethod()&lt;/code&gt;, that mixes the handling of the files – even doing things like &lt;code&gt;os.fsync()&lt;/code&gt; – while manipulating structured data. This is definitely not a good design, especially for a library, as it turns out reusing the function in different context is near impossible.&lt;/p&gt;
&lt;h2&gt;Race conditions&lt;/h2&gt;
&lt;p&gt;There are race conditions, for example in &lt;code&gt;create()&lt;/code&gt; (see added comment):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if os.path.exists(path):
  raise InvalidConfiguration(&quot;File %s already exists!&quot; % path)
fh = None
try:
  # TOO LATE I ALREADY CREATED THE FILE IN ANOTHER PROCESS YOU ARE GOING TO
  # FAIL WITHOUT GIVING ANY USEFUL INFORMATION TO THE CALLER :-(
  fh = open(path,&apos;wb&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That code should be:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;try:
  fh = os.fdopen(os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL), &apos;wb&apos;)
except OSError as e:
  if e.errno == errno.EEXIST:
    raise InvalidConfiguration(&quot;File %s already exists!&quot; % path)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to avoid any race condition.&lt;/p&gt;
&lt;h2&gt;Unwanted optimization&lt;/h2&gt;
&lt;p&gt;We saw earlier the &lt;code&gt;fetch()&lt;/code&gt; function that is barely useful, so let&apos;s take a look at the &lt;code&gt;file_fetch()&lt;/code&gt; function that it&apos;s calling.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def file_fetch(fh, fromTime, untilTime, now = None):
  header = __readHeader(fh)
[...]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first thing the function does is to read the header from the file handler.&lt;/p&gt;
&lt;p&gt;Let&apos;s take a look at that function:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def __readHeader(fh):
  info = __headerCache.get(fh.name)
  if info:
    return info

  originalOffset = fh.tell()
  fh.seek(0)
  packedMetadata = fh.read(metadataSize)

  try:
    (aggregationType,maxRetention,xff,archiveCount) = struct.unpack(metadataFormat,packedMetadata)
  except:
    raise CorruptWhisperFile(&quot;Unable to read header&quot;, fh.name)
[...]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first thing the function does is to look into a cache. Why is there a cache?&lt;/p&gt;
&lt;p&gt;It actually caches the header based with an index based on the file path (&lt;code&gt;fh.name&lt;/code&gt;). Except that if one for example decide not to use file and cheat using &lt;code&gt;StringIO&lt;/code&gt;, then it does not have any name attribute. So this code path will raise an &lt;code&gt;AttributeError&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;One has to set a fake name manually on the &lt;code&gt;StringIO&lt;/code&gt; instance, and it must be unique so nobody messes with the cache&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import StringIO

packedMetadata = &amp;lt;some source&amp;gt;
fh = StringIO.StringIO(packedMetadata)
fh.name = &quot;myfakename&quot;
header = __readHeader(fh)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The cache may actually be useful when accessing files, but it&apos;s definitely useless when not using files. But it&apos;s not necessarily true that the complexity (even if small) that the cache adds is worth it. I doubt most of &lt;em&gt;whisper&lt;/em&gt; based tools are long run processes, so the cache that is really used when accessing the files is the one handled by the operating system kernel, and this one is going to be much more efficient anyway, and shared between processed. There&apos;s also no expiry of that cache, which could end up of tons of memory used and wasted.&lt;/p&gt;
&lt;h2&gt;Docstrings&lt;/h2&gt;
&lt;p&gt;None of the docstrings are written in a a parsable syntax like &lt;a href=&quot;http://sphinx-doc.org/&quot;&gt;Sphinx&lt;/a&gt;. This means you cannot generate any documentation in a nice format that a developer using the library could read easily.&lt;/p&gt;
&lt;p&gt;The documentation is also not up to date:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def fetch(path,fromTime,untilTime=None,now=None):
  &quot;&quot;&quot;fetch(path,fromTime,untilTime=None)
[...]
&quot;&quot;&quot;

def create(path,archiveList,xFilesFactor=None,aggregationMethod=None,sparse=False,useFallocate=False):
  &quot;&quot;&quot;create(path,archiveList,xFilesFactor=0.5,aggregationMethod=&apos;average&apos;)
[...]
&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is something that could be avoided if a proper format was picked to write the docstring. A tool cool be used to be noticed when there&apos;s a diversion between the actual function signature and the documented one, like missing an argument.&lt;/p&gt;
&lt;h2&gt;Duplicated code&lt;/h2&gt;
&lt;p&gt;Last but not least, there&apos;s a lot of code that is duplicated around in the scripts provided by &lt;em&gt;whisper&lt;/em&gt; in its &lt;code&gt;bin&lt;/code&gt; directory. Theses scripts should be very lightweight and be using the &lt;code&gt;console_scripts&lt;/code&gt; facility of &lt;em&gt;setuptools&lt;/em&gt;, but they actually contains a lot of (untested) code. Furthermore, some of that code is partially duplicated from the &lt;code&gt;whisper.py&lt;/code&gt; library which is against &lt;a href=&quot;http://en.wikipedia.org/wiki/Don&apos;t_repeat_yourself&quot;&gt;DRY&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;There are a few more things that made me stop considering &lt;em&gt;whisper&lt;/em&gt;, but these are part of the &lt;em&gt;whisper&lt;/em&gt; features, not necessarily code quality. One can also point out that the code is very condensed and hard to read, and that&apos;s a more general problem about how it is organized and abstracted.&lt;/p&gt;
&lt;p&gt;A lot of these defects are actually points that made me start writing &lt;a href=&quot;https://thehackerguidetopython.com&quot;&gt;The Hacker&apos;s Guide to Python&lt;/a&gt; a year ago.&lt;br /&gt;
Running into this kind of code makes me think it was a really good idea to write a book on advice to write better Python code!&lt;/p&gt;
</content:encoded></item><item><title>Tracking OpenStack contributions in GitHub</title><link>https://julien.danjou.info/blog/tracking-openstack-contributions-in-github/</link><guid isPermaLink="true">https://julien.danjou.info/blog/tracking-openstack-contributions-in-github/</guid><description>I&apos;ve switched my Git repositories to GitHub recently, and started to watch my contributions statistics, which were very low considering I spend my days hacking on open source software, especially.</description><pubDate>Tue, 19 Aug 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve switched my Git repositories to &lt;a href=&quot;https://github.com&quot;&gt;GitHub&lt;/a&gt; recently, and started to watch my contributions statistics, which were very low considering I spend my days hacking on open source software, especially &lt;a href=&quot;https://openstack.org&quot;&gt;OpenStack&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/octocat-on-openstack-2.png&quot; alt=&quot;octocat-on-openstack-2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;OpenStack hosts its Git repositories on its own infrastructure at &lt;a href=&quot;http://git.openstack.org&quot;&gt;git.openstack.org&lt;/a&gt;, but also mirrors them on GitHub. Logically, I was expecting GitHub to track my commits there too, as I&apos;m using the same email address everywhere.&lt;/p&gt;
&lt;p&gt;It turns out that it was not the case, and the &lt;a href=&quot;https://help.github.com/articles/why-are-my-contributions-not-showing-up-on-my-profile&quot;&gt;help page about that&lt;/a&gt; on GitHub describes the rule in place to compute statistics. Indeed, according to GitHub, I had no relations to the OpenStack repositories, as I never forked them nor opened a pull request on them (OpenStack uses &lt;a href=&quot;http://review.openstack.org&quot;&gt;Gerrit&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Starring a repository is enough to build a relationship between a user and a repository, so this is was the only thing needed to inform GitHub that I have contributed to those repositories. Considering OpenStack has hundreds of repositories, I decided to star them all by using a small Python script using &lt;a href=&quot;https://pypi.python.org/pypi/pygithub&quot;&gt;pygithub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And voilà, &lt;a href=&quot;https://github.com/jd&quot;&gt;my statistics&lt;/a&gt; are now including all my contributions to OpenStack!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/github-openstack-stats.png&quot; alt=&quot;github-openstack-stats&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>OpenStack Ceilometer and the Gnocchi experiment</title><link>https://julien.danjou.info/blog/openstack-ceilometer-the-gnocchi-experiment/</link><guid isPermaLink="true">https://julien.danjou.info/blog/openstack-ceilometer-the-gnocchi-experiment/</guid><description>A little more than 2 years ago, the Ceilometer project was launched inside the OpenStack ecosystem. Its main objective was to measure OpenStack cloud platforms in order to provide data and mechanisms.</description><pubDate>Mon, 18 Aug 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A little more than 2 years ago, the &lt;a href=&quot;http://launchpad.net/ceilometer&quot;&gt;Ceilometer&lt;/a&gt; project was launched inside the OpenStack ecosystem. Its main objective was to measure OpenStack cloud platforms in order to provide data and mechanisms for functionalities such as billing, alarming or capacity planning.&lt;/p&gt;
&lt;p&gt;In this article, I would like to relate what I&apos;ve been doing with other Ceilometer developers in the last 5 months. I&apos;ve lowered my involvement in Ceilometer itself directly to concentrate on solving one of its biggest issue at the source, and I think it&apos;s largely time to take a break and talk about it.&lt;/p&gt;
&lt;h2&gt;Ceilometer early design&lt;/h2&gt;
&lt;p&gt;For the last years, Ceilometer didn&apos;t change in its core architecture. Without diving too much in all its parts, one of the early design decision was to build the metering around a data structure we called &lt;strong&gt;samples&lt;/strong&gt;. A sample is generated each time Ceilometer measures something. It is composed of a few fields, such as the the resource id that is metered, the user and project id owning that resources, the meter name, the measured value, a timestamp and a few free-form metadata. Each time Ceilometer measures something, one of its components (an agent, a pollster…) constructs and emits a sample headed for the storage component that we call the &lt;strong&gt;collector&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This collector is responsible for storing the samples into a database. The Ceilometer collector uses a pluggable storage system, meaning that you can pick any database system you prefer. Our original implementation has been based on MongoDB from the beginning, but we then added a SQL driver, and people contributed things such as HBase or DB2 support.&lt;/p&gt;
&lt;p&gt;The REST API exposed by Ceilometer allows to execute various reading requests on this data store. It can returns you the list of resources that have been measured for a particular project, or compute some statistics on metrics. Allowing such a large panel of possibilities and having such a flexible data structure allows to do a lot of different things with Ceilometer, as you can almost query the data in any mean you want.&lt;/p&gt;
&lt;h2&gt;The scalability issue&lt;/h2&gt;
&lt;p&gt;We soon started to encounter scalability issues in many of the read requests made via the REST API. A lot of the requests requires the data storage to do full scans of all the stored samples. Indeed, the fact that the API allows you to filter on any fields and also on the free-form metadata (meaning non indexed key/values tuples) has a terrible cost in terms of performance (as pointed before, the metadata are attached to each &lt;em&gt;sample&lt;/em&gt; generated by Ceilometer and is stored as is). That basically means that the &lt;em&gt;sample&lt;/em&gt; data structure is stored in most drivers in just one table or collection, in order to be able to scan them at once, and there&apos;s no good &quot;perfect&quot; sharding solution, making data storage scalability painful.&lt;/p&gt;
&lt;p&gt;It turns out that the Ceilometer REST API is unable to handle most of the requests in a timely manner as most operations are &lt;em&gt;O(n)&lt;/em&gt; where &lt;em&gt;n&lt;/em&gt; is the number of samples recorded (see &lt;a href=&quot;http://en.wikipedia.org/wiki/Big_O_notation&quot;&gt;big O notation&lt;/a&gt; if you&apos;re unfamiliar with it). That number of samples can grow very rapidly in an environment of thousands of metered nodes and with a data retention of several weeks. There is a few optimizations to make things smoother in general cases fortunately, but as soon as you run specific queries, the API gets barely usable.&lt;/p&gt;
&lt;p&gt;During this last year, as the Ceilometer PTL, I discovered these issues first hand since a lot of people were feeding me back with this kind of testimony. We engaged several blueprints to improve the situation, but it was soon clear to me that this was not going to be enough anyway.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/unacceptable.jpg&quot; alt=&quot;unacceptable&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Thinking outside the box&lt;/h2&gt;
&lt;p&gt;Unfortunately, the PTL job doesn&apos;t leave him enough time to work on the actual code nor to play with anything new. I was coping with most of the project bureaucracy and I wasn&apos;t able to work on any good solution to tackle the issue at its root. Still, I had a few ideas that I wanted to try and as soon as I stepped down from the PTL role, I stopped working on Ceilometer itself to try something new and to think a bit outside the box.&lt;/p&gt;
&lt;p&gt;When one takes a look at what have been brought recently in Ceilometer, they can see the idea that Ceilometer actually needs to handle 2 types of data: events and metrics.&lt;/p&gt;
&lt;p&gt;Events are data generated when something happens: an instance start, a volume is attached, or an HTTP request is sent to an REST API server. These are events that Ceilometer needs to collect and store. Most OpenStack components are able to send such events using the notification system built into &lt;em&gt;&lt;a href=&quot;https://wiki.openstack.org/wiki/Oslo/Messaging&quot;&gt;oslo.messaging&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Metrics is what Ceilometer needs to store but that is not necessarily tied to an event. Think about an instance CPU usage, a router network bandwidth usage, the number of images that Glance is storing for you, etc… These are not events, since nothing is happening. These are facts, states we need to meter.&lt;/p&gt;
&lt;p&gt;Computing statistics for billing or capacity planning requires both of these data sources, but they should be distinct. Based on that assumption, and the fact that Ceilometer was getting support for storing events, I started to focus on getting the metric part right.&lt;/p&gt;
&lt;p&gt;I had been a system administrator for a decade before jumping into OpenStack development, so I know a thing or two on how monitoring is done in this area, and what kind of technology operators rely on. I also know that there&apos;s still no silver bullet – this made it a good challenge.&lt;/p&gt;
&lt;p&gt;The first thing that came to my mind was to use some kind of time-series database, and export its access via a REST API – as we do in all OpenStack services. This should cover the metric storage pretty well.&lt;/p&gt;
&lt;h2&gt;Cooking Gnocchi&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-logo-old-2.jpg&quot; alt=&quot;gnocchi-logo-old-2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;At the end of April 2014, this led met to start a new project code-named Gnocchi. For the record, the name was picked after confusing so many times the OpenStack Marconi project, reading OpenStack Macaroni instead. At least one OpenStack project should have a &quot;pasta&quot; name, right?&lt;/p&gt;
&lt;p&gt;The point of having a new project and not send patches on Ceilometer, was that first I had no clue if it was going to make something that would be any better, and second, being able to iterate more rapidly without being strongly coupled with the release process.&lt;/p&gt;
&lt;p&gt;The first prototype started around the following idea: what you want is to meter things. That means storing a list of tuples of (timestamp, value) for it. I&apos;ve named these things &quot;entities&quot;, as no assumption are made on what they are. An entity can represent the temperature in a room or the CPU usage of an instance. The service shouldn&apos;t care and should be agnostic in this regard.&lt;/p&gt;
&lt;p&gt;One feature that we discussed for several OpenStack summits in the Ceilometer sessions, was the idea of doing aggregation. Meaning, aggregating samples over a period of time to only store a smaller amount of them. These are things that time-series format such as the &lt;a href=&quot;http://oss.oetiker.ch/rrdtool/&quot;&gt;RRDtool&lt;/a&gt; have been doing for a long time on the fly, and I decided it was a good trail to follow.&lt;/p&gt;
&lt;p&gt;I assumed that this was going to be a requirement when storing metrics into Gnocchi. The user would need to provide what kind of archiving it would need: 1 second precision over a day, 1 hour precision over a year, or even both.&lt;/p&gt;
&lt;p&gt;The first driver written to achieve that and store those metrics inside Gnocchi was based on &lt;a href=&quot;http://graphite.wikidot.com/whisper&quot;&gt;whisper&lt;/a&gt;. Whisper is the file format used to store metrics for the &lt;a href=&quot;http://graphite.wikidot.com/&quot;&gt;Graphite&lt;/a&gt; project. For the actual storage, the driver uses Swift, which has the advantages to be part of OpenStack and scalable.&lt;/p&gt;
&lt;p&gt;Storing metrics for each entities in a different &lt;em&gt;whisper&lt;/em&gt; file and putting them in Swift turned out to have a fantastic algorithm complexity: it was &lt;em&gt;O(1)&lt;/em&gt;. Indeed, the complexity needed to store and retrieve metrics doesn&apos;t depends on the number of metrics you have nor on the number of things you are metering. Which is already a huge win compared to the current Ceilometer collector design.&lt;/p&gt;
&lt;p&gt;However, it turned out that &lt;em&gt;whisper&lt;/em&gt; has a few limitations that I was unable to circumvent in any manner. I needed to patch it to remove a lot of its assumption about manipulating file, or that everything is relative to now (&lt;code&gt;time.time()&lt;/code&gt;). I&apos;ve started to hack on that in my own fork, but… then everything broke. The &lt;em&gt;whisper&lt;/em&gt; project code base is, well, not the state of the art, and have 0 unit test. I was starring at a huge effort to transform &lt;em&gt;whisper&lt;/em&gt; into the time-series format I wanted, without being sure I wasn&apos;t going to break everything (remember, no test coverage).&lt;/p&gt;
&lt;p&gt;I decided to take a break and look into alternatives, and stumbled upon &lt;a href=&quot;http://pandas.pydata.org/&quot;&gt;Pandas&lt;/a&gt;, a data manipulation and statistics library for Python. Turns out that Pandas support time-series natively, and that it could do a lot of the smart computation needed in Gnocchi. I built a new file format leveraging Pandas for computing the time-series and named it &lt;strong&gt;carbonara&lt;/strong&gt; (a wink to both the &lt;a href=&quot;https://github.com/graphite-project/carbon&quot;&gt;Carbon&lt;/a&gt; project and pasta, how clever!). The code is quite small (a third of &lt;em&gt;whisper&lt;/em&gt;&apos;s, 200 SLOC vs 600 SLOC), does not have many of the &lt;em&gt;whisper&lt;/em&gt; limitations and… it has test coverage. These Carbonara files are then, in the same fashion, stored into Swift containers.&lt;/p&gt;
&lt;p&gt;Anyway, Gnocchi storage driver system is designed in the same spirit that the rest of OpenStack and Ceilometer storage driver system. It&apos;s a plug-in system with an API, so anyone can write their own driver. Eoghan Glynn has already started to write a &lt;a href=&quot;http://influxdb.com/&quot;&gt;InfluxDB&lt;/a&gt; driver, working closely with the upstream developer of that database. Dina Belova started to write an &lt;a href=&quot;http://opentsdb.net/&quot;&gt;OpenTSDB&lt;/a&gt; driver. This helps to make sure the API is designed directly in the right way.&lt;/p&gt;
&lt;h2&gt;Handling resources&lt;/h2&gt;
&lt;p&gt;Measuring individual entities is great and needed, but you also need to link them with resources. When measuring the temperature and the number of a people in a room, it is useful to link these 2 separate entities to a resource, in that case the room, and give a name to these relations, so one is able to identify what attribute of the resource is actually measured. It is also important to provide the possibility to store attributes on these resources, such as their owners, the time they started and ended their existence, etc.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-relationship.png&quot; alt=&quot;gnocchi-relationship&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Once this list of resource is collected, the next step is to list and filter them, based on any criteria. One might want to retrieve the list of resources created last week or the list of instances hosted on a particular node right now.&lt;/p&gt;
&lt;p&gt;Resources also need to be specialized. Some resources have attributes that must be stored in order for filtering to be useful. Think about an instance name or a router network.&lt;/p&gt;
&lt;p&gt;All of these requirements led to to the design of what&apos;s called the &lt;em&gt;indexer&lt;/em&gt;. The indexer is responsible for indexing entities, resources, and link them together. The initial implementation is based on &lt;a href=&quot;http://sqlalchemy.org&quot;&gt;SQLAlchemy&lt;/a&gt; and should be pretty efficient. It&apos;s easy enough to index the most requested attributes (columns), and they are also correctly typed.&lt;/p&gt;
&lt;p&gt;We plan to establish a model for all known OpenStack resources (instances, volumes, networks, …) to store and index them into the Gnocchi indexer in order to request them in an efficient way from one place. The generic resource class can be used to handle generic resources that are not tied to OpenStack. It&apos;d be up to the users to store extra attributes.&lt;/p&gt;
&lt;p&gt;Dropping the free form metadata we used to have in Ceilometer makes sure that querying the indexer is going to be efficient and scalable.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-classes.png&quot; alt=&quot;gnocchi-classes&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;REST API&lt;/h2&gt;
&lt;p&gt;All of this is exported via a REST API that was partially designed and documented in the &lt;a href=&quot;http://git.openstack.org/cgit/openstack/ceilometer-specs/tree/specs/juno/gnocchi.rst&quot;&gt;Gnocchi specification in the Ceilometer repository&lt;/a&gt;; though the spec is not up-to-date yet. We plan to auto-generate the documentation from the code as we are currently doing in Ceilometer.&lt;/p&gt;
&lt;p&gt;The REST API is pretty easy to use, and you can use it to manipulate entities and resources, and request the information back.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnocchi-architecture.png&quot; alt=&quot;gnocchi-architecture&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Roadmap &amp;amp; Ceilometer integration&lt;/h2&gt;
&lt;p&gt;All of this plan has been exposed and discussed with the Ceilometer team&lt;br /&gt;
during the last &lt;a href=&quot;https://www.openstack.org/summit/openstack-summit-atlanta-2014/&quot;&gt;OpenStack summit in Atlanta&lt;/a&gt; in May 2014, for the Juno release. I led a session about this entire concept, and convinced the team that using Gnocchi for our metric storage would be a good approach to solve the Ceilometer collector scalability issue.&lt;/p&gt;
&lt;p&gt;It was decided to conduct this project experiment in parallel of the current Ceilometer collector for the time being, and see where that would lead the project to.&lt;/p&gt;
&lt;h2&gt;Early benchmarks&lt;/h2&gt;
&lt;p&gt;Some engineers from Mirantis did a few benchmarks around Ceilometer and also against an early version of Gnocchi, and Dina Belova presented them to us during the mid-cycle sprint we organized in Paris in early July.&lt;/p&gt;
&lt;p&gt;The following graph sums up pretty well the current Ceilometer performance issue. The more you feed it with metrics, the more slow it becomes.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/image03.png&quot; alt=&quot;image03&quot; /&gt;&lt;/p&gt;
&lt;p&gt;For Gnocchi, while the numbers themselves are not fantastic, what is interesting is that all the graphs below show that the performances are stable without correlation with the number of resources, entities or measures. This proves that, indeed, most of the code is built around a complexity of &lt;em&gt;O(1)&lt;/em&gt;, and not &lt;em&gt;O(n)&lt;/em&gt; anymore.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/image00.png&quot; alt=&quot;image00&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;https://julien.danjou.info/content/images/03/image01.png&quot; alt=&quot;image01&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;https://julien.danjou.info/content/images/03/image04.png&quot; alt=&quot;image04&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/image05.png&quot; alt=&quot;image05&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/image06.png&quot; alt=&quot;image06&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/clement-drawing-gnocchi.jpg&quot; alt=&quot;clement-drawing-gnocchi&quot; /&gt;&lt;/p&gt;
&lt;p&gt;While the Juno cycle is being wrapped-up for most projects, including Ceilometer, Gnocchi development is still ongoing. Fortunately, the composite architecture of Ceilometer allows a lot of its features to be replaced by some other code dynamically. That, for example, enables Gnocchi to provides a Ceilometer dispatcher plugin for its collector, without having to ship the actual code in Ceilometer itself. That should help the development of Gnocchi to not be slowed down by the release process for now.&lt;/p&gt;
&lt;p&gt;The Ceilometer team aims to provide Gnocchi as a sort of technology preview with the Juno release, allowing it to be deployed along and plugged with Ceilometer. We&apos;ll discuss how to integrate it in the project in a more permanent and strong manner probably during the &lt;a href=&quot;https://www.openstack.org/summit/openstack-paris-summit-2014/&quot;&gt;OpenStack Summit for Kilo&lt;/a&gt; that will take place next November in Paris.&lt;/p&gt;
</content:encoded></item><item><title>OpenStack Design Summit Juno, from a Ceilometer point of view</title><link>https://julien.danjou.info/blog/openstack-summit-juno-ceilometer/</link><guid isPermaLink="true">https://julien.danjou.info/blog/openstack-summit-juno-ceilometer/</guid><description>Last week was the OpenStack Design Summit in Atlanta, GA where we, developers, discussed and designed the new OpenStack release (Juno) coming up.</description><pubDate>Fri, 30 May 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last week was the &lt;a href=&quot;https://www.openstack.org/summit/openstack-summit-atlanta-2014/&quot;&gt;OpenStack Design Summit&lt;/a&gt; in Atlanta, GA where we, developers, discussed and designed the new OpenStack release (Juno) coming up. I&apos;ve been there mainly to discuss Ceilometer upcoming developments.&lt;/p&gt;
&lt;p&gt;The summit has been great. It was my third OpenStack design summit, and the first one not being a PTL, meaning it was a largely more relaxed summit for me!&lt;/p&gt;
&lt;p&gt;On Monday, we started by a 2.5 hours meeting with Ceilometer core developers and contributors about the Gnocchi experimental project that I&apos;ve started a few weeks ago. It was a great and productive afternoon, and allowed me to introduce and cover this topic extensively, something that would not have been possible in the allocated session we had later in the week.&lt;/p&gt;
&lt;p&gt;Ceilometer had his design sessions running mainly during Wednesday. We noted a lot of things and commented during the sessions in our &lt;a href=&quot;https://wiki.openstack.org/wiki/Summit/Juno/Etherpads#Ceilometer&quot;&gt;Etherpads instances&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here is a short summary of the sessions I&apos;ve attended.&lt;/p&gt;
&lt;h2&gt;Scaling the central agent&lt;/h2&gt;
&lt;p&gt;I was in charge of the first session, and introduced the work that was done so far in the scaling of the central agent. Six months ago, during the Havana summit, I proposed to scale the central agent by distributing the tasks among several node, using a library to handle the group membership aspect of it. That led to the creation of the &lt;a href=&quot;https://pypi.python.org/pypi/tooz&quot;&gt;tooz&lt;/a&gt; library that we worked on at eNovance during the last 6 months.&lt;/p&gt;
&lt;p&gt;Now that we have this foundation available, Cyril Roelandt started to replace the Ceilometer alarming job repartition code by Taskflow and Tooz. Starting with the central agent is simpler and will be a first proof of concept to be used by the central agent then. We plan to get this merged for Juno.&lt;/p&gt;
&lt;p&gt;For the central agent, the same work needs to be done, but since it&apos;s a bit more complicated, it will be done after the alarming evaluators are converted.&lt;/p&gt;
&lt;h2&gt;Test strategy&lt;/h2&gt;
&lt;p&gt;The next session discussed the test strategy and how we could improve Ceilometer unit and functional testing. There is a lot in this area to be done, and this is going to be one of the main focus of the team in the upcoming weeks. Having Tempest tests run was a goal for Havana, and even if we made a lot of progress, we&apos;re still no there yet.&lt;/p&gt;
&lt;h2&gt;Complex queries and per-user/project data collection&lt;/h2&gt;
&lt;p&gt;This session, led by Ildikó Váncsa, was about adding finer-grained configuration into the pipeline configuration to allow per-user and per-project data retrieval. This was not really controversial, though how to implement this exactly is still to be discussed, but the idea was well received. The other part of the session was about adding more in the complex queries feature provided by the v2 API.&lt;/p&gt;
&lt;h2&gt;Rethinking Ceilometer as a Time-Series-as-a-Service&lt;/h2&gt;
&lt;p&gt;This was my main session, the reason we met on Monday for a few hours, and one of the most promising session – I hope – of the week.&lt;/p&gt;
&lt;p&gt;It appears that the way Ceilometer designed its API and storage backends a long time ago is now a problem to scale the data storage. Also, the events API we introduced in the last release partially overlaps some of the functionality provided by the samples API that causes us scaling troubles.&lt;/p&gt;
&lt;p&gt;Therefore, I&apos;ve started to rethink the Ceilometer API by building it as a time series read/write service, letting the audit part of our previous sample API to the event subsystem. After a few researches and experiments, I&apos;ve designed a new project called &lt;a href=&quot;https://wiki.openstack.org/Gnocchi&quot;&gt;Gnocchi&lt;/a&gt;, which provides exactly that functionality in a hopefully scalable way.&lt;/p&gt;
&lt;p&gt;Gnocchi is split in two parts: a time series API and its driver, and a resource indexing API with its own driver. Having two distinct driver sets allows it to use different technologies to store each data type in the best storage engine possible. The canonical driver for time series handling is based on &lt;a href=&quot;http://pandas.pydata.org/&quot;&gt;Pandas&lt;/a&gt; and &lt;a href=&quot;https://launchpad.net/swift&quot;&gt;Swift&lt;/a&gt;. The canonical resource indexer driver&lt;br /&gt;
is based on &lt;a href=&quot;http://sqlalchemy.org&quot;&gt;SQLAlchemy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The idea and project was well received and looked pretty exciting to most people. Our hope is to design a version 3 of the Ceilometer API around Gnocchi at some point during the Juno cycle, and have it ready as some sort of preview for the final release.&lt;/p&gt;
&lt;h2&gt;Revisiting the Ceilometer data model&lt;/h2&gt;
&lt;p&gt;This session led by Alexei Kornienko, kind of echoed the previous session, as it clearly also tried to address the Ceilometer scalability issue, but in a different way.&lt;/p&gt;
&lt;p&gt;Anyway, the SQL driver limitations have been discussed and Mehdi Abaakouk implemented some of the suggestions during the week, so we should very soon see more performances in Ceilometer with the current default storage driver.&lt;/p&gt;
&lt;h2&gt;Ceilometer devops session&lt;/h2&gt;
&lt;p&gt;We organized this session to get feedbacks from the devops community about deploying Ceilometer. It was very interesting, and the list of things we could improve is long, and I think will help us to drive our future efforts.&lt;/p&gt;
&lt;h2&gt;SNMP inspectors&lt;/h2&gt;
&lt;p&gt;This session, led by Lianhao Lu, discussed various details of the future of SNMP support in Ceilometer.&lt;/p&gt;
&lt;h2&gt;Alarm and logs improvements&lt;/h2&gt;
&lt;p&gt;This mixed session, led by Nejc Saje and Gordon Chung, was about possible improvements on the alarm evaluation system provided by Ceilometer, and making logging in Ceilometer more effective. Both half-sessions were interesting and led to several ideas on how to improve both systems.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Considering the current QA problems with Ceilometer, Eoghan Glynn, the new &lt;em&gt;Project Technical Leader&lt;/em&gt; for Ceilometer, clearly indicated that this will be the main focus of the release cycle.&lt;/p&gt;
&lt;p&gt;Personally, I will be focused on working on Gnocchi, and will likely be joined by others in the next weeks. Our idea is to develop a complete solution with a high velocity in the next weeks, and then works on its integration with Ceilometer itself.&lt;/p&gt;
</content:encoded></item><item><title>Making of The Hacker&apos;s Guide to Python</title><link>https://julien.danjou.info/blog/making-of-the-hacker-guide-to-python/</link><guid isPermaLink="true">https://julien.danjou.info/blog/making-of-the-hacker-guide-to-python/</guid><description>As promised, today I would like to write a bit about the making of The Hacker&apos;s Guide to Python. It has been a very interesting experimentation, and I think it is worth sharing it with you.</description><pubDate>Wed, 07 May 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As promised, today I would like to write a bit about the making of &lt;a href=&quot;https://thehackerguidetopython.com&quot;&gt;The Hacker&apos;s Guide to Python&lt;/a&gt;. It has been a very interesting experimentation, and I think it is worth sharing it with you.&lt;/p&gt;
&lt;h2&gt;The inspiration&lt;/h2&gt;
&lt;p&gt;All started out at the beginning of August 2013. I was spending my summer, as the rest of the year, hacking on OpenStack.&lt;/p&gt;
&lt;p&gt;As years passed, I got more and more deeply involved in the various tools that we either built or contributed to within the OpenStack community. And I somehow got the feeling that my experience with Python, the way we used it inside OpenStack and other applications during these last years was worth sharing. Worth writing something bigger than a few blog posts.&lt;/p&gt;
&lt;p&gt;The OpenStack project is doing code reviews, and therefore so did I for almost two years. That inspired a lot of topics, like &lt;a href=&quot;https://julien.danjou.info/blog/guide-python-static-class-abstract-methods&quot;&gt;the definitive guide to method decorators&lt;/a&gt; that I wrote at the time I started the hacker&apos;s guide. Stumbling upon the same mistakes or misunderstanding over and over is, somehow, inspiring.&lt;/p&gt;
&lt;p&gt;I also stumbled upon &lt;a href=&quot;http://nathanbarry.com&quot;&gt;Nathan Barry&lt;/a&gt;&apos;s blog and book &lt;a href=&quot;http://nathanbarry.com/authority/&quot;&gt;Authority&lt;/a&gt; which were very helpful to get started and some sort of guideline.&lt;/p&gt;
&lt;p&gt;All of that brought me enough ideas to start writing a book about Python software development for people already familiar with the language.&lt;/p&gt;
&lt;h2&gt;The writing&lt;/h2&gt;
&lt;p&gt;The first thing I started to do is to list all the topics I wanted to write about. The list turned out to have subjects that had no direct interest for a practical guide. For example, on one hand, very few developers know in details how metaclasses work, but on the other hand, I never had to write a metaclass during these last years. That&apos;s the kind of subject I decided not to write about, dropped all subjects that I felt were not going to help my reader to be more productive. Even if they could be technically interesting.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/the-hacker-guide-to-python-opened.png&quot; alt=&quot;the-hacker-guide-to-python-opened&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Then, I gathered all problems I saw during the code reviews I did during these last two years. Some of them I only recalled in the days following the beginning of that project. But I kept adding them to the table of contents, reorganizing stuff as needed.&lt;/p&gt;
&lt;p&gt;After a couple of weeks, I had a pretty good overview of the contents that there I will write about. All I had to do was to fill in the blank (that sounds so simple now).&lt;/p&gt;
&lt;p&gt;The entire writing of the took hundred hours spread from August to November, during my spare time. I had to stop all my other side projects for that.&lt;/p&gt;
&lt;h2&gt;The interviews&lt;/h2&gt;
&lt;p&gt;While writing the book, I tried to parallelize every thing I could. That included asking people for interviews to be included in the book. I already had a pretty good list of the people I wanted to feature in the book, so I took some time as soon as possible to ask them, and send them detailed questions.&lt;/p&gt;
&lt;p&gt;I discovered two categories of interviewees. Some of them were very fast to answer (≤ 1 week), and others were much, much slower. A couple of them even set up Git repositories to answer the questions, because that probably looked like an entire project to them. :-) So I had to not lose sight and kindly ask from time to time if everything was alright, and at some point started to kindly set some deadline.&lt;/p&gt;
&lt;p&gt;In the end, the quality of the answers was awesome, and I like to think that was because I picked the right people!&lt;/p&gt;
&lt;h2&gt;The proof-reading&lt;/h2&gt;
&lt;p&gt;Once the book was finished, I somehow needed to have people proof-reading it. This was probably the hardest part of this experiment. I needed two different types of reviews: technical reviews, to check that the content was correct and interesting, and language review. That one is even more important since English is not my native language.&lt;/p&gt;
&lt;p&gt;Finding technical reviewers seemed easy at first, as I had ton of contacts that I identified as being able to review the book. I started by asking a few people if they would be comfortable reading a simple chapter and giving me feedbacks. I started to do that in September: having the writing and the reviews done in parallel was important to me in order to minimize latency and the book&apos;s release delay.&lt;/p&gt;
&lt;p&gt;All people I contacted answered positively that they would be interested in doing a technical review of a chapter. So I started to send chapters to them. But in the end, only 20% replied back. And even after that, a large portion stopped reviewing after a couple of chapters.&lt;/p&gt;
&lt;p&gt;Don&apos;t get me wrong: you can&apos;t be mad at people not wanting to spend their spare time in book edition like you do.&lt;/p&gt;
&lt;p&gt;However, from the few people that gave their time to review a few chapters, I got tremendous feedback, at all level. That&apos;s something that was very important and that helped a lot getting confident. Writing a book alone for months without having anyone taking a look upon your shoulder can make you doubt that you are creating something worth it.&lt;/p&gt;
&lt;p&gt;As far as English proof-reading, I went ahead and used &lt;a href=&quot;http://odesk.com&quot;&gt;ODesk&lt;/a&gt; to recruit a professional proof-reader. I looked for people with the right skills: a good English level (being a native English speaker at least), be able to understand what the book was about, and being able to work with correct delays. I had mixed results from the people I hired, but I guess that&apos;s normal. The only error I made was not to parallelize those reviews enough, so I probably lost a couple of months on&lt;br /&gt;
that.&lt;/p&gt;
&lt;h2&gt;The toolchain&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/the-hacker-guide-to-python-2.png&quot; alt=&quot;the-hacker-guide-to-python-2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;While writing the book, I did a few breaks to build a toolchain. What I call a toolchain is set of tools used to render the final PDF, EPUB and MOBI files of the guide.&lt;/p&gt;
&lt;p&gt;After some research, I decided to settle on &lt;a href=&quot;http://www.methods.co.nz/asciidoc/&quot;&gt;AsciiDoc&lt;/a&gt;, using the &lt;a href=&quot;http://www.docbook.org&quot;&gt;DocBook&lt;/a&gt; output, which is then being transformed to LaTeX, and then to PDF, or either to EPUB directly. I rely on &lt;a href=&quot;http://calibre-ebook.com/&quot;&gt;Calibre&lt;/a&gt; to convert the EPUB file to MOBI. It took me a few hours to do what I wanted, using some magic LaTeX tricks to have a proper render, but it was worth it and I&apos;m particularly happy with the result.&lt;/p&gt;
&lt;p&gt;For the cover design, I asked my talented friend &lt;a href=&quot;http://nicolas-veyret.com/&quot;&gt;Nicolas&lt;/a&gt; to do something for me, and he designed the wonderful cover and its little snake!&lt;/p&gt;
&lt;h2&gt;The publishing&lt;/h2&gt;
&lt;p&gt;Publishing in an interesting topic people kept asking me about. This is what I had to answer a few dozens of time:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;Who is your editor?&quot;&lt;/li&gt;
&lt;li&gt;&quot;Me.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I never had any plan for asking an editor to publish this book. Nowadays, asking an editor to publish a book feels to me like asking a major company to publish a CD. It feels awkward.&lt;/p&gt;
&lt;p&gt;However, don&apos;t get me wrong: there can be a few upsides of having an editor. They will find reviewers and review your book for you. Having the book review handled for you is probably a very good thing, considering how it was hard to me to get that in place. It can be especially important for a technical book.&lt;/p&gt;
&lt;p&gt;Also, your book may end up in brick and mortar stores and be part of a collection, both improving visibility. That may improve your book&apos;s selling, though the editor and all the intermediaries are going to keep the largest amount of the money anyway.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;Oh, you will publish it yourself, great. So you will print it and sell it to people?&quot;&lt;/li&gt;
&lt;li&gt;&quot;Not really.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&apos;ve heard good stories about people using &lt;a href=&quot;http://gumroad.com&quot;&gt;Gumroad&lt;/a&gt; to sell electronic contents, so after looking for competitors in that market, I picked them. I also had the idea to sell the book with Bitcoins, so I settled on &lt;a href=&quot;http://coinbase.com&quot;&gt;Coinbase&lt;/a&gt;, because they have a nice API to do that.&lt;/p&gt;
&lt;p&gt;Setting up everything was quite straight-forward, especially with Gumroad. It only took me a few hours to do so. Writing the Coinbase application took a few hours too.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;Oh, you will sell it only as an ebook? That&apos;s too bad. You need a paper version. Many people will want a paper version.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My initial plan was to only sell online an electronic version. On the other hand, since I kept hearing that a printed version should exist, I decided to give it a try. I chose to work with &lt;a href=&quot;http://lulu.com&quot;&gt;Lulu&lt;/a&gt; because I knew people using it, and it was pretty simple to set up.&lt;/p&gt;
&lt;h2&gt;The launch&lt;/h2&gt;
&lt;p&gt;Once I had everything ready, I built the selling page and connected everything between Mailchimp, Gumroad, Coinbase, Google Analytics, etc.&lt;/p&gt;
&lt;p&gt;Writing the launch email was really exciting. I used Mailchimp feature to send the launch mail in several batches, just to have some margin in case of a sudden last minute problem. But everything went fine. Hurrah!&lt;/p&gt;
&lt;p&gt;I distributed around 200 copies of the ebook in the first 48 hours, for about $5000. That covered all the cost I had from the writing the book, and even more, so I was already pretty happy with the launch.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/thgtp-sell-graph.png&quot; alt=&quot;thgtp-sell-graph&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Retrospective&lt;/h2&gt;
&lt;p&gt;In retrospect, something that I didn&apos;t do the best way possible is probably to build a solid mailing list of people interested, and to build an important anticipation and incentive to buy the book at launch date. My mailing list counted around 1500 people subscribed because they were interested in the launch of the book subscribed; in the end, probably only 10-15% of them bought the book during the launch, which is probably a bit lower than what I could expect.&lt;/p&gt;
&lt;p&gt;But more than a month later, I distributed in total almost 500 copies of the book (including physical units) for more than $10000, so I tend to think that this was a success. I still sell a few copies of the book each weeks, but the number are small compared to the launch.&lt;/p&gt;
&lt;p&gt;I sold less than 10 copies of the ebook using Bitcoins, and I admit I&apos;m a bit disappointed and surprised about that.&lt;/p&gt;
&lt;p&gt;Physical copies represent 10% of the book distribution. It&apos;s probably a lot lower than most people that pushed me to do it thought it would be. But it is still higher of what I thought it would be. So I still would advise to have a paperback version of your book. At least because it&apos;s nice to have it&lt;br /&gt;
in your library.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/thgtp-paperback.jpg&quot; alt=&quot;thgtp-paperback&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I only got positive feedbacks, a few typo notices, and absolutely no refund demand, which I really find amazing.&lt;/p&gt;
&lt;p&gt;The good news is also that I&apos;ve been contacted with a couple of Korean and Chinese editors to get the book translated and published in those countries. If everything goes well, the book should be translated in the upcoming months and be available on these markets in 2015!&lt;/p&gt;
&lt;p&gt;If you didn&apos;t get a copy, &lt;a href=&quot;https://thehackerguidetopython.com&quot;&gt;it&apos;s still time to do so&lt;/a&gt;!&lt;/p&gt;
</content:encoded></item><item><title>Doing A/B testing with Apache httpd</title><link>https://julien.danjou.info/blog/a-b-testing-with-apache/</link><guid isPermaLink="true">https://julien.danjou.info/blog/a-b-testing-with-apache/</guid><description>When I started writing the landing page for The Hacker&apos;s Guide to Python, I wanted to try new things at the same time.</description><pubDate>Sun, 06 Apr 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When I started writing the landing page for &lt;a href=&quot;https://thehackerguidetopython.com&quot;&gt;The Hacker&apos;s Guide to Python&lt;/a&gt;, I wanted to try new things at the same time. I read about A/B testing a while ago, and I figured it was a good opportunity to test it out.&lt;/p&gt;
&lt;h2&gt;A/B testing&lt;/h2&gt;
&lt;p&gt;If you do not know what A/B testing is about, take a quick look at the &lt;a href=&quot;http://en.wikipedia.org/wiki/A/B_testing&quot;&gt;Wikipedia page on that subject&lt;/a&gt;. Long story short, the idea is to serve two different version of a page to your visitors and check which one is getting the most success. When you found which version is better, you can definitely switch to it.&lt;/p&gt;
&lt;p&gt;In the case of my book, I used that technique on the pre-launch page where people were able to subscribe to the newsletter. I didn&apos;t have a lot of things I wanted to test out on that page, so I just used that approach on the subtitle, being either &quot;Learn everything you need to build a successful Python project&quot; or &quot;It&apos;s time you make the most out of Python&quot;.&lt;/p&gt;
&lt;p&gt;Statistically, each version would be served half of the time, so both would get the same number of view. I then would build statistics about which page was attracting the most subscribers. With the results I would be able to switch definitively to that version of the landing page.&lt;/p&gt;
&lt;h2&gt;Technical design&lt;/h2&gt;
&lt;p&gt;My Web site, this Web site, is entirely static and served by &lt;a href=&quot;http://httpd.apache.org/&quot;&gt;Apache httpd&lt;/a&gt;. I didn&apos;t want to use any dynamic page, language or whatever. Mainly because I didn&apos;t want to have something else to install and maintain just for that on my server.&lt;/p&gt;
&lt;p&gt;It turns out that Apache httpd is powerful enough to implement such a feature. There are different ways to build it, and I&apos;m going to describe my choices here.&lt;/p&gt;
&lt;p&gt;The first thing to pick is a way to balance the display of the page. You need to find a way so that if you get 100 visitors, around 50 will see the version A of your page, and around 50 will see the version B of the page.&lt;/p&gt;
&lt;p&gt;You could use a random number generator, pick a random number for each visitor, and decides which page he&apos;s going to see. But it turns out that I didn&apos;t find a way to do that with Apache httpd at first sight.&lt;/p&gt;
&lt;p&gt;My second thought was to pick the client IP address. But it&apos;s not such a good idea, because if you got visitors from, for example, people behind a company firewall, they are all going to be served the same page, so that kind of kills the statistics.&lt;/p&gt;
&lt;p&gt;Finally, I picked time based balancing: if you visit the page on a second that is even, you get version A of the page, and if you visit the page on a second that is odd, you get version B. Simple, and so far nothing proves there are more visitors on even than odd seconds, or vice-versa.&lt;/p&gt;
&lt;p&gt;The next thing is to always serve the same page to a returning visitor. I mean that if the visitor comes back later and get a different version, that&apos;s cheating. I decided the system should always serve the same page once a visitor &quot;picked&quot; a version. To do that, it&apos;s simple enough, you just have to use cookies to store the page the visitor has been attributed, and then use that cookie if he comes back.&lt;/p&gt;
&lt;h2&gt;Implementation&lt;/h2&gt;
&lt;p&gt;To do that in Apache httpd, I used the powerful &lt;a href=&quot;http://httpd.apache.org/docs/current/mod/mod_rewrite.html&quot;&gt;mod_rewrite&lt;/a&gt; that is shipped with it. I put 2 files in the books directory, named either &quot;the-hacker-guide-to-python-a.html&quot; and &quot;the-hacker-guide-to-python-b.html&quot; that got served when you requested &quot;&lt;a href=&quot;https://thehackerguidetopython.com&quot;&gt;https://thehackerguidetopython.com&lt;/a&gt;&quot;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RewriteEngine On
RewriteBase /books

## If there&apos;s a cookie called thgtp-pre-version set,
## use its value and serve the page
RewriteCond %{HTTP_COOKIE} thgtp-pre-version=([^;])
RewriteRule ^the-hacker-guide-to-python$ %{REQUEST_FILENAME}-%1.html [L]

## No cookie yet and…
RewriteCond %{HTTP_COOKIE} !thgtp-pre-version=([^;]+)
## … the number of seconds of the time right now is even
RewriteCond %{TIME_SEC} [02468]$
## Then serve the page A and store &quot;a&quot; in a cookie
RewriteRule ^the-hacker-guide-to-python$ %{REQUEST_FILENAME}-a.html [cookie=thgtp-pre-version:a:julien.danjou.info,L]

## No cookie yet and…
RewriteCond %{HTTP_COOKIE} !thgtp-pre-version=([^;]+)
## … the number of seconds of the time right now is odd
RewriteCond %{TIME_SEC} [13579]$
## Then serve the page B and store &quot;b&quot; in a cookie
RewriteRule ^the-hacker-guide-to-python$ %{REQUEST_FILENAME}-b.html [cookie=thgtp-pre-version:b:julien.danjou.info,L]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that few lines, it worked flawlessly.&lt;/p&gt;
&lt;h2&gt;Results&lt;/h2&gt;
&lt;p&gt;The results were very good, as it worked perfectly. Combined with Google Analytics, I was able to follow the score of each page. It turns out that testing this particular little piece of content of the page was, as expected, really useless. The final score didn&apos;t allow to pick any winner. Which also kind of proves that the system worked perfectly.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/google-analytics-ab-testing-thgtp.png&quot; alt=&quot;google-analytics-ab-testing-thgtp&quot; /&gt;&lt;/p&gt;
&lt;p&gt;But it still was an interesting challenge!&lt;/p&gt;
</content:encoded></item><item><title>The Hacker&apos;s Guide to Python released!</title><link>https://julien.danjou.info/blog/the-hacker-guide-to-python-has-been-released/</link><guid isPermaLink="true">https://julien.danjou.info/blog/the-hacker-guide-to-python-has-been-released/</guid><description>And done! It took me just 8 months to do this entire book project around Python. From the first day I started writing to today, where I finally publish and sell – almost entirely – myself this book. I</description><pubDate>Tue, 25 Mar 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;And done! It took me just 8 months to do this entire book project around Python. From the first day I started writing to today, where I finally publish and sell – almost entirely – myself this book. I&apos;m really proud of what I&apos;ve achieved so far, as this was something totally new to me.&lt;/p&gt;
&lt;p&gt;Doing all of that has been a great adventure, and I&apos;ll promise I&apos;ll write something about that later on. A making of.&lt;/p&gt;
&lt;p&gt;For now, you can enjoy reading the book and learn a bit more about Python. I really hope it&apos;ll help you bring your Python-fu to a new level, and help you build great projects!&lt;/p&gt;
&lt;p&gt;Go check it out, and since this is first day of sale, enjoy 20% off by using the offer code &lt;strong&gt;THGTP20&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/the-hacker-guide-to-python-4.png&quot; alt=&quot;Cover of The Hacker&apos;s Guide to Python&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>OpenStack Ceilometer Icehouse-2 milestone released</title><link>https://julien.danjou.info/blog/openstack-ceilometer-icehouse-2-milestone-released/</link><guid isPermaLink="true">https://julien.danjou.info/blog/openstack-ceilometer-icehouse-2-milestone-released/</guid><description>Yesterday, the second milestone of the Icehouse development branch of Ceilometer has been released and is now available for testing and download. This means the first half of the OpenStack Icehouse de</description><pubDate>Fri, 24 Jan 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Yesterday, the second milestone of the Icehouse development branch of Ceilometer has been released and is now available for testing and download. This means the first half of the OpenStack &lt;em&gt;Icehouse&lt;/em&gt; development has&lt;br /&gt;
passed!&lt;/p&gt;
&lt;h2&gt;New features&lt;/h2&gt;
&lt;p&gt;For the &lt;a href=&quot;https://launchpad.net/ceilometer/+milestone/icehouse-1&quot;&gt;Icehouse-1 milestone&lt;/a&gt;, we barely had enough time to implement 2 blueprints. We almost did a better job this time, but finally only &lt;a href=&quot;https://launchpad.net/ceilometer/+milestone/icehouse-2&quot;&gt;2 blueprints were implemented&lt;/a&gt; again. This is really far from what we planned initially. The infrastructure slowdown issues and the lower number of reviews is probably the root cause here.&lt;/p&gt;
&lt;p&gt;Anyway, Ceilometer now offers a &lt;a href=&quot;https://blueprints.launchpad.net/ceilometer/+spec/specify-event-api&quot;&gt;REST API to accesses the stored event&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The initial work to replace the &lt;code&gt;/v2/meters&lt;/code&gt; endpoint with something more RESTy has started with the &lt;a href=&quot;https://blueprints.launchpad.net/ceilometer/+spec/sample-api&quot;&gt;implementation of &lt;code&gt;/v2/samples&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Bug fixes&lt;/h2&gt;
&lt;p&gt;Thirty-one bugs were fixed, though most of them might not interest you so I won&apos;t elaborate too much on that. Go read &lt;a href=&quot;https://launchpad.net/ceilometer/+milestone/icehouse-2&quot;&gt;the list&lt;/a&gt; if you are curious.&lt;/p&gt;
&lt;h2&gt;Toward Icehouse 3&lt;/h2&gt;
&lt;p&gt;We now have 29 blueprints targeting the &lt;a href=&quot;https://launchpad.net/ceilometer/+milestone/icehouse-3&quot;&gt;Ceilometer&apos;s third Icehouse milestone&lt;/a&gt;, with some of them are already started and ready to merge. However, it&apos;s likely that we won&apos;t make all of them. As usual, the priority should indicate how confident we are that we want and need a feature. Still, it&apos;s likely the roadmap will be adjusted in the upcoming weeks. I&apos;ll try to make sure we&apos;ll get there without too much trouble for the 6th March 2013. Stay tuned!&lt;/p&gt;
</content:encoded></item><item><title>Databases integration testing strategies with Python</title><link>https://julien.danjou.info/blog/db-integration-testing-strategies-python/</link><guid isPermaLink="true">https://julien.danjou.info/blog/db-integration-testing-strategies-python/</guid><description>The Ceilometer project supports various database backend that can be used as storage. Among them are MongoDB, SQLite MySQL, PostgreSQL, HBase, DB2… All Ceilometer&apos;s code is unit tested, but when.</description><pubDate>Mon, 06 Jan 2014 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;http://launchpad.net/ceilometer&quot;&gt;Ceilometer&lt;/a&gt; project supports various database backend that can be used as storage. Among them are &lt;a href=&quot;http://www.mongodb.org/&quot;&gt;MongoDB&lt;/a&gt;, &lt;a href=&quot;http://sqlite.org&quot;&gt;SQLite&lt;/a&gt; &lt;a href=&quot;http://mysql.com&quot;&gt;MySQL&lt;/a&gt;, &lt;a href=&quot;http://postgresql.org&quot;&gt;PostgreSQL&lt;/a&gt;, &lt;a href=&quot;http://hbase.apache.org/&quot;&gt;HBase&lt;/a&gt;, DB2… All Ceilometer&apos;s code is unit tested, but when dealing with external storage services, one cannot be sure that the code is really working. You could be inserting data with an incorrect SQL statement, or in the wrong table. Only having the real database storage running and being used can tell you that.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/python_db_tests.png&quot; alt=&quot;python_db_tests&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Over the months, we developed integration testing on top of our unit testing to validate that our storage drivers are able to deal with real world databases. That is not really different from generic &lt;a href=&quot;http://en.wikipedia.org/wiki/Integration_testing&quot;&gt;integration testing&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Integration testing is about plugging all the pieces of your software all together and running. In what I call &quot;database integration testing&quot;, the pieces will be both your software and the database system that you are going to rely on.&lt;/p&gt;
&lt;p&gt;The only difference here is that one of the module is not coming from the application itself but is an external project. The type of database that you use (RDBMS, NoSQL…) does not matter. Taking a step back, what I will describe here could also apply to a lot of other different software modules, even something that would not be a database sytem at all.&lt;/p&gt;
&lt;h3&gt;Writing tests for integration&lt;/h3&gt;
&lt;p&gt;Presumably, your Python application has unit tests. In order to test against a database back-end, you need to write a few specific classes of tests that will use the database subsystem for real. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import unittest
import os
import sqlalchemy

class TestDB(unittest.TestCase):
    def setUp(self):
       url = os.getenv(&quot;DB_TEST_URL&quot;)
       if not url:
           self.skipTest(&quot;No database URL set&quot;)
       self.engine = sqlalchemy.create_engine(url)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code will try to fetch the database URL to use from an environment variable, and then will rely on &lt;a href=&quot;http://sqlalchemy.org&quot;&gt;SQLAlchemy&lt;/a&gt; to create a database connection.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import unittest
import os
import sqlalchemy

import myapp

class TestDB(unittest.TestCase):
    def setUp(self):
       url = os.getenv(&quot;DB_TEST_URL&quot;)
       if not url:
           self.skipTest(&quot;No database URL set&quot;)
       self.engine = sqlalchemy.create_engine(url)

    def test_foobar(self):
        self.assertTrue(myapp.store_integer(self.engine, 42))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can then add as many tests as you want using the connection stored in &lt;code&gt;self.engine&lt;/code&gt;. If no test database URL is, the tests will be skipped; however that decision is up to you. You may want to have these tests always run and fail if they can&apos;t be run.&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;setUp()&lt;/code&gt; method, you may also need to do more work, like create a database and delete a database.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import unittest
import os
import sqlalchemy

class TestDB(unittest.TestCase):
    def setUp(self):
       url = os.getenv(&quot;DB_TEST_URL&quot;)
       if not url:
           self.skipTest(&quot;No database URL set&quot;)
       self.engine = sqlalchemy.create_engine(url)
       self.connection = self.engine.connect()
       self.connection.execute(&quot;CREATE DATABASE testdb&quot;)

    def tearDown(self):
        self.connection.execute(&quot;DROP DATABASE testdb&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will make sure that the database you need is clean and ready to be used to testing.&lt;/p&gt;
&lt;h3&gt;Launching modules, a.k.a. databases&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/postgresql.png&quot; alt=&quot;postgresql&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The main problem we encountered when building integration testing with databases, is to find a way to start them. Most users are used to start them system-wide with some sort of init script, but when running sandboxed tests, that is not really a good option. Browsing the documentation of each storage allowed us to find a way to start them in foreground and control them &quot;interactively&quot; via a shell script.&lt;/p&gt;
&lt;p&gt;The following is a script that you can use to run Python tests using &lt;a href=&quot;http://nose.readthedocs.org/&quot;&gt;nose&lt;/a&gt; and is heavily inspired by the one we wrote for Ceilometer.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash
set -e

clean_exit() {
    local error_code=&quot;$?&quot;
    kill -9 $(jobs -p) &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 || true
    rm -rf &quot;$PGSQL_DATA&quot;
    return $error_code
}

check_for_cmd () {
    if ! which &quot;$1&quot; &amp;gt;/dev/null 2&amp;gt;&amp;amp;1
    then
        echo &quot;Could not find $1 command&quot; 1&amp;gt;&amp;amp;2
        exit 1
    fi
}

wait_for_line () {
    while read line
    do
        echo &quot;$line&quot; | grep -q &quot;$1&quot; &amp;amp;&amp;amp; break
    done &amp;lt; &quot;$2&quot;
    # Read the fifo for ever otherwise process would block
    cat &quot;$2&quot; &amp;gt;/dev/null &amp;amp;
}

check_for_cmd postgres

trap &quot;clean_exit&quot; EXIT

## Start PostgreSQL process for tests
PGSQL_DATA=`mktemp -d /tmp/PGSQL-XXXXX`
PGSQL_PATH=`pg_config --bindir`
${PGSQL_PATH}/initdb ${PGSQL_DATA}
mkfifo ${PGSQL_DATA}/out
${PGSQL_PATH}/postgres -F -k ${PGSQL_DATA} -D ${PGSQL_DATA} &amp;amp;&amp;gt; ${PGSQL_DATA}/out &amp;amp;
## Wait for PostgreSQL to start listening to connections
wait_for_line &quot;database system is ready to accept connections&quot; ${PGSQL_DATA}/out
export DB_TEST_URL=&quot;postgresql:///?host=${PGSQL_DATA}&amp;amp;dbname=template1&quot;

## Run the tests
nosetests
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you use &lt;a href=&quot;http://tox.readthedocs.org&quot;&gt;tox&lt;/a&gt; to automatize your test run, you can use this scripts (I call it &lt;code&gt;run-test.sh&lt;/code&gt;) in your &lt;code&gt;tox.ini&lt;/code&gt; file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[testenv]
commands = {toxinidir}/run-tests.sh {posargs}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/mysql.png&quot; alt=&quot;mysql&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Most databases are able to be run in some sort of standalone mode where you can connect to them using a either a Unix domain socket, or a fixed port. Here are the snippet used in Ceilometer to run with MongoDB and MySQL:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## Start MongoDB process for tests
MONGO_DATA=$(mktemp -d /tmp/MONGODB-XXXXX)
MONGO_PORT=29000
mkfifo ${MONGO_DATA}/out
mongod --maxConns 32 --nojournal --noprealloc --smallfiles --quiet --noauth --port ${MONGO_PORT} --dbpath &quot;${MONGO_DATA}&quot; --bind_ip localhost &amp;amp;&amp;gt;${MONGO_DATA}/out &amp;amp;
## Wait for Mongo to start listening to connections
wait_for_line &quot;waiting for connections on port ${MONGO_PORT}&quot; ${MONGO_DATA}/out
export DB_TEST_URL=&quot;mongodb://localhost:${MONGO_PORT}/testdb&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/mongodb.png&quot; alt=&quot;mongodb&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## Start MySQL process for tests
MYSQL_DATA=$(mktemp -d /tmp/MYSQL-XXXXX)
mkfifo ${MYSQL_DATA}/out
mysqld --datadir=${MYSQL_DATA} --pid-file=${MYSQL_DATA}/mysql.pid --socket=${MYSQL_DATA}/mysql.socket --skip-networking --skip-grant-tables &amp;amp;&amp;gt; ${MYSQL_DATA}/out &amp;amp;
## Wait for MySQL to start listening to connections
wait_for_line &quot;mysqld: ready for connections.&quot; ${MYSQL_DATA}/out
export DB_TEST_URL=&quot;mysql://root@localhost/testdb?unix_socket=${MYSQL_DATA}/mysql.socket&amp;amp;charset=utf8&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The mechanism is always the same. We create a &lt;em&gt;fifo&lt;/em&gt; with &lt;code&gt;mkfifo&lt;/code&gt;, and then run the database daemon with the output redirected to that fifo. We then read from it until we find a line stating the the database is ready to be used. At that point, we can continue and start running the tests. You have to read continuously from the fifo, otherwise the process writing to it will block. We redirect the output to &lt;code&gt;/dev/null&lt;/code&gt;, but you could also redirect it to a different log file, or not at all.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: &lt;a href=&quot;http://www.die-welt.net/&quot;&gt;Evgeni Golov&lt;/a&gt; pointed it exists a &lt;a href=&quot;https://alioth.debian.org/scm/loggerhead/pkg-postgresql/postgresql-common/trunk/view/head:/pg_virtualenv&quot;&gt;pg_virtualenv&lt;/a&gt; for PostgreSQL and &lt;a href=&quot;https://github.com/evgeni/my_virtualenv&quot;&gt;my_virtualenv&lt;/a&gt; for MySQL that does the same kind of thing, but with more bells and whistles.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;One step further: using parallelism and scenarios&lt;/h3&gt;
&lt;p&gt;The described approach is quite simple, as it only support one database type. When using an abstraction layer, such as SQLAlchemy, it would be a good idea to run all these tests against different RDBMS, such as MySQL and PostgreSQL for example.&lt;/p&gt;
&lt;p&gt;The snippet above allows to run both RDBMS in parallel, but the classic approach of unit tests does not allow that. Using one scenario for each database backend would be a great idea. To that end, you can use the &lt;a href=&quot;https://launchpad.net/testscenarios&quot;&gt;testscenarios&lt;/a&gt; library.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import unittest
import os
import sqlalchemy
import testscenarios

load_tests = testscenarios.load_tests_apply_scenarios

class TestDB(unittest.TestCase):
    scenarios = [
        (&apos;mysql&apos;, dict(database_connection=os.getenv(&quot;MYSQL_TEST_URL&quot;)),
        (&apos;postgresql&apos;, dict(database_connection=os.getenv(&quot;PGSQL_TEST_URL&quot;)),
    ]

    def setUp(self):
       if not self.database_connection:
           self.skipTest(&quot;No database URL set&quot;)
       self.engine = sqlalchemy.create_engine(self.database_connection)
       self.connection = self.engine.connect()
       self.connection.execute(&quot;CREATE DATABASE testdb&quot;)

    def tearDown(self):
        self.connection.execute(&quot;DROP DATABASE testdb&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ python -m subunit.run test_scenario | subunit2pyunit
test_scenario.TestDB.test_foobar(mysql)
test_scenario.TestDB.test_foobar(mysql) ... ok
test_scenario.TestDB.test_foobar(postgresql)
test_scenario.TestDB.test_foobar(postgresql) ... ok

---------------------------------------------------------
Ran 2 tests in 0.061s

OK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To speed up tests run, you could also run the test in parallel. It can be intesting as you&apos;ll be able to spread the workload among a lot of different CPUs. However, note that it can require a different database for each test or a locking mechanism to be in place. It&apos;s likely that your tests won&apos;t be able to work altogether at the same time on only one database.&lt;/p&gt;
&lt;p&gt;(Both usage of scenarios and parallelism in testing will be covered in &lt;a href=&quot;https://thehackerguidetopython.com&quot;&gt;The Hacker&apos;s Guide to Python&lt;/a&gt;,&lt;br /&gt;
in case you wonder.)&lt;/p&gt;
</content:encoded></item><item><title>OpenStack Design Summit Icehouse, from a Ceilometer point of view</title><link>https://julien.danjou.info/blog/openstack-summit-icehouse-ceilometer/</link><guid isPermaLink="true">https://julien.danjou.info/blog/openstack-summit-icehouse-ceilometer/</guid><description>Last week was the OpenStack Design Summit Icehouse in Hong-Kong where we, OpenStack developers, discussed and designed the new OpenStack release (Icehouse) that is coming up.</description><pubDate>Wed, 13 Nov 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last week was the &lt;a href=&quot;http://www.openstack.org/summit/openstack-summit-hong-kong-2013/&quot;&gt;OpenStack Design Summit Icehouse&lt;/a&gt; in Hong-Kong where we, OpenStack developers, discussed and designed the new OpenStack release (Icehouse) that is coming up.&lt;/p&gt;
&lt;p&gt;The week has been wonderful. It was my second OpenStack design summit, and I loved it. Bumping into various people I&apos;ve never met so far and worked with online was a real pleasure. As it was to meet again with fellow OpenStack developers! The event organisation was great, as were the parties. :-)&lt;/p&gt;
&lt;p&gt;On the last day, I had the chance to present a talk with Eoghan Glynn and Nick Barcet how we built the auto-scaling feature in Heat, implementing the &quot;alarming&quot; feature needed in Ceilometer.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/ods_icehouse_ceilometer_heat_nijaba_eglynn_jd.jpg&quot; alt=&quot;ods_icehouse_ceilometer_heat_nijaba_eglynn_jd&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Design sessions&lt;/h2&gt;
&lt;p&gt;This time, Ceilometer design sessions were spread on 3 days. Everything we talked about has its &lt;a href=&quot;https://wiki.openstack.org/wiki/Summit/Icehouse/Etherpads#Ceilometer&quot;&gt;Etherpad instance&lt;/a&gt;. The discussions were interesting, and the amount of feedback gathered is big and is going to be very useful.&lt;/p&gt;
&lt;p&gt;There&apos;s a lot of people and companies using Ceilometer now, and the project is getting more and more traction in general. There&apos;s a lot of different way to use it and to bend it to one&apos;s needs. Considering the amount of features and options that is provided, building functionality with a genericized approach it making Ceilometer useful for a lot of different and interesting use-cases.&lt;/p&gt;
&lt;h2&gt;Icehouse roadmap&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://blueprints.launchpad.net/ceilometer/icehouse&quot;&gt;list of blueprints targeting Icehouse is available&lt;/a&gt;, but not yet complete. I expect people to start filling this list in the next days. If you want to propose blueprints, you&apos;re free to do so and inform us about it so we can validate it. The same applies if you wish to implement one of them!&lt;/p&gt;
&lt;p&gt;Thereafter, I try to guess what the roadmap will look like in the upcoming weeks for Ceilometer based on the discussion we had last week during the summit.&lt;/p&gt;
&lt;h3&gt;Events management&lt;/h3&gt;
&lt;p&gt;A lot of work is going to be put into event management. Ceilometer plans to store notifications sent using &lt;em&gt;oslo.messaging&lt;/em&gt; by OpenStack projects. Some work already got merge for Havana, but the API part and future improvements and ideas will continue to flow into the Icehouse release.&lt;/p&gt;
&lt;h3&gt;Agents and group management&lt;/h3&gt;
&lt;p&gt;A lot has been discussed around the polling agents and around the alarm evaluator agent.&lt;/p&gt;
&lt;p&gt;The current state of the &lt;em&gt;ceilometer-central-agent&lt;/em&gt; disallows any kind of high-availability and load-balancing, as the polling task are kept and scheduled on only one node.&lt;/p&gt;
&lt;p&gt;The high-availability part is already covered by a custom mechanism built into &lt;em&gt;ceilometer-alarm-evaluator&lt;/em&gt;, but it came clear to us that a more generic approach is needed. A lot of other projects needs this kind of functionality, and a pattern have been pointed out. A &lt;a href=&quot;https://wiki.openstack.org/wiki/Oslo/blueprints/service-sync&quot;&gt;blueprint about group membership&lt;/a&gt; has been discussed in an Oslo session, and will end into a new Python library written to solve this in Ceilometer and in other projects.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://wiki.openstack.org/wiki/Taskflow&quot;&gt;TaskFlow&lt;/a&gt; will also probably be leveraged to solve the task distribution issue.&lt;/p&gt;
&lt;h3&gt;Documentation&lt;/h3&gt;
&lt;p&gt;Since a few weeks, Ceilometer auto-generates its &lt;a href=&quot;http://api.openstack.org/api-ref-metering.html&quot;&gt;API reference documentation&lt;/a&gt; using &lt;a href=&quot;https://git.openstack.org/cgit/stackforge/sphinxcontrib-docbookrestapi/&quot;&gt;sphinxcontrib-docbookrestapi&lt;/a&gt; that parses our API code that uses &lt;a href=&quot;https://pypi.python.org/pypi/WSME&quot;&gt;WSME&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We also want to start writing a user guide, and we&apos;ll do that inside our own repository. That way, I hope that we will be the first project in OpenStack to require documentation to be incorporated into every patch that&apos;s being sent to Ceilometer. This is the best way to assure that nothing can be changed nor added without being accompanied with a documentation update.&lt;/p&gt;
&lt;h3&gt;Tempest testing&lt;/h3&gt;
&lt;p&gt;Testing of Ceilometer already has been a subject during the previous design summit about testing. We already put a large effort on Tempest testing in this last cycle, but we encountered a lot of small issues that we had to tackle to achieve something. Some Ceilometer basic tests are already on their way into Tempest, so this is something that is going to be achieved very soon.&lt;/p&gt;
&lt;p&gt;Ultimately, I would also want Ceilometer moving towards providing its own set of Tempest tests as part of the code base. That way, it&apos;d be as easy for core reviewers to refuse a patch if it doesn&apos;t provide functional tests as it is to refuse it if it doesn&apos;t provide unit tests. As we&apos;ll do for the documentation.&lt;/p&gt;
&lt;h3&gt;API improvements&lt;/h3&gt;
&lt;p&gt;Once again, a few API improvements will probably be implemented, like aggregation or the ability to specify multiple queries with &lt;em&gt;OR&lt;/em&gt; and &lt;em&gt;AND&lt;/em&gt; operators.&lt;/p&gt;
&lt;h3&gt;Roll-up, archiving of data&lt;/h3&gt;
&lt;p&gt;There seems to be interest in archiving and rolling-up the data stored by Ceilometer, so work in this area is to be expected. Supporting multiple data storage driver in parallel seems to be something that needs to be done for this and other aspects of Ceilometer feature set.&lt;/p&gt;
&lt;h3&gt;Alarming&lt;/h3&gt;
&lt;p&gt;The alarming feature set is already big, and the work that has been accomplished pretty amazing. A few improvements will be made, as retrieving better metrics and building better statistics (exclusion of low quality data points).&lt;/p&gt;
</content:encoded></item><item><title>Python 3.4 single dispatch, a step into generic functions</title><link>https://julien.danjou.info/blog/python-3-4-single-dispatch-generic-function/</link><guid isPermaLink="true">https://julien.danjou.info/blog/python-3-4-single-dispatch-generic-function/</guid><description>I love to say that Python is a nice subset of Lisp, and I discover that it&apos;s getting even more true as time passes.</description><pubDate>Tue, 17 Sep 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I love to say that Python is a nice subset of Lisp, and I discover that it&apos;s getting even more true as time passes. Recently, I&apos;ve stumbled upon the &lt;a href=&quot;http://python.org/dev/peps/pep-0443/&quot;&gt;PEP 443&lt;/a&gt; that describes a way to dispatch generic functions, in a way that looks like what CLOS, the Common Lisp Object System, provides.&lt;/p&gt;
&lt;h2&gt;What are generic functions&lt;/h2&gt;
&lt;p&gt;If you come from the Lisp world, this won&apos;t be something new to you. The Lisp object system provides a really good way to define and handle method dispatching. It&apos;s a base of the Common Lisp object system. For my own pleasure to see Lisp code in a Python post, I&apos;ll show you how generic methods work in Lisp first.&lt;/p&gt;
&lt;p&gt;To begin, let&apos;s define a few very simple classes.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(defclass snare-drum ()
  ())

(defclass cymbal ()
  ())

(defclass stick ()
  ())

(defclass brushes ()
  ())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This defines a few classes: &lt;code&gt;snare-drum&lt;/code&gt;, &lt;code&gt;symbal&lt;/code&gt;, &lt;code&gt;stick&lt;/code&gt; and &lt;code&gt;brushes&lt;/code&gt;, without any parent class nor attribute. These classes compose a drum kit, and we can combine them to play sound. So we define a &lt;code&gt;play&lt;/code&gt; method that takes two arguments, and returns a sound (as a string).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(defgeneric play (instrument accessory)
  (:documentation &quot;Play sound with instrument and accessory.&quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This only defines a generic method: it has no body, and cannot be called with any instance yet. At this stage, we only inform the object system that the method is generic and can be then implemented with various type of arguments. We&apos;ll start by implementing versions of this method that knows how to play with the snare-drum.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(defmethod play ((instrument snare-drum) (accessory stick))
  &quot;POC!&quot;)

(defmethod play ((instrument snare-drum) (accessory brushes))
  &quot;SHHHH!&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we just defined concrete methods with code. They also takes two arguments: &lt;code&gt;instrument&lt;/code&gt; which is an instance of &lt;code&gt;snare-drum&lt;/code&gt; and &lt;code&gt;accessory&lt;/code&gt; that is an instance of &lt;code&gt;stick&lt;/code&gt; or &lt;code&gt;brushes&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;At this stage, you should note the first difference with object system as built into language like Python: the method isn&apos;t tied to any class in particular. The methods are &lt;em&gt;generic&lt;/em&gt;, and any class can implement them, or not.&lt;/p&gt;
&lt;p&gt;Let&apos;s try it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;* (play (make-instance &apos;snare-drum) (make-instance &apos;stick))
&quot;POC!&quot;

* (play (make-instance &apos;snare-drum) (make-instance &apos;brushes))
&quot;SHHHH!&quot;

* (play (make-instance &apos;cymbal) (make-instance &apos;stick))
debugger invoked on a SIMPLE-ERROR in thread
#&amp;lt;THREAD &quot;main thread&quot; RUNNING {1002ADAF23}&amp;gt;:
  There is no applicable method for the generic function
    #&amp;lt;STANDARD-GENERIC-FUNCTION PLAY (2)&amp;gt;
  when called with arguments
    (#&amp;lt;CYMBAL {1002B801D3}&amp;gt; #&amp;lt;STICK {1002B82763}&amp;gt;).

Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.

restarts (invokable by number or by possibly-abbreviated name):
  0: [RETRY] Retry calling the generic function.
  1: [ABORT] Exit debugger, returning to top level.

((:METHOD NO-APPLICABLE-METHOD (T)) #&amp;lt;STANDARD-GENERIC-FUNCTION PLAY (2)&amp;gt; #&amp;lt;CYMBAL {1002B801D3}&amp;gt; #&amp;lt;STICK {1002B82763}&amp;gt;) [fast-method]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you see, the function called depends on the class of the arguments. The object systems &lt;strong&gt;dispatch&lt;/strong&gt; the function calls to the right function for us, depending on the arguments classes. If we call &lt;code&gt;play&lt;/code&gt; with instances that are not know to the object system, an error will be thrown.&lt;/p&gt;
&lt;p&gt;Inheritance is also supported and the equivalent (but more powerful and less error prone) equivalent of Python&apos;s &lt;code&gt;super()&lt;/code&gt; is available via &lt;code&gt;(call-next-method)&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(defclass snare-drum () ())
(defclass cymbal () ())

(defclass accessory () ())
(defclass stick (accessory) ())
(defclass brushes (accessory) ())

(defmethod play ((c cymbal) (a accessory))
  &quot;BIIING!&quot;)

(defmethod play ((c cymbal) (b brushes))
  (concatenate &apos;string &quot;SSHHHH!&quot; (call-next-method)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, we define the &lt;code&gt;stick&lt;/code&gt; and &lt;code&gt;brushes&lt;/code&gt; classes as subclass of the &lt;code&gt;accessory&lt;/code&gt; class. The &lt;code&gt;play&lt;/code&gt; method defined will return the sound &lt;em&gt;BIIING!&lt;/em&gt; regardless of the accessory instance that is used to play the cymbal. Except in the case where it&apos;s a &lt;code&gt;brushes&lt;/code&gt; instance; only the most precise method is always called. The &lt;code&gt;(call-next-method)&lt;/code&gt; function is used to call the closest parent method, in this case that would be the method returning _&quot;BIIING!&quot;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;* (play (make-instance &apos;cymbal) (make-instance &apos;stick))
&quot;BIIING!&quot;

* (play (make-instance &apos;cymbal) (make-instance &apos;brushes))
&quot;SSHHHH!BIIING!&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that CLOS is also able to dispatch on object instances themself by using the &lt;code&gt;eql&lt;/code&gt; specializer.&lt;/p&gt;
&lt;p&gt;But if you&apos;re really curious about all features CLOS provides, I suggest you read the &lt;a href=&quot;http://www.aiai.ed.ac.uk/~jeff/clos-guide.html&quot;&gt;brief guide to CLOS by Jeff Dalton&lt;/a&gt; as a starter.&lt;/p&gt;
&lt;h2&gt;Python implementation&lt;/h2&gt;
&lt;p&gt;Python implements a simpler equivalence of this workflow with the &lt;code&gt;singledispatch&lt;/code&gt; function. It will be provided with Python 3.4 as part of the &lt;code&gt;functools&lt;/code&gt; module. Here&apos;s a rough equivalence of the above Lisp program.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import functools

class SnareDrum(object): pass
class Cymbal(object): pass
class Stick(object): pass
class Brushes(object): pass

@functools.singledispatch
def play(instrument, accessory):
    raise NotImplementedError(&quot;Cannot play these&quot;)

@play.register(SnareDrum)
def _(instrument, accessory):
    if isinstance(accessory, Stick):
        return &quot;POC!&quot;
    if isinstance(accessory, Brushes):
        return &quot;SHHHH!&quot;
    raise NotImplementedError(&quot;Cannot play these&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We define our four classes, and a base &lt;code&gt;play&lt;/code&gt; function that raises &lt;code&gt;NotImplementedError&lt;/code&gt;, indicating that by default we don&apos;t know what to do. We can then write specialized version of this function with a first instrument, the &lt;code&gt;SnareDrum&lt;/code&gt;. We then check for the accessory type that we get, and return the appropriate sound or raise &lt;code&gt;NotImplementedError&lt;/code&gt; again if we don&apos;t know what to do with it.&lt;/p&gt;
&lt;p&gt;If we run it, it works as expected:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; play(SnareDrum(), Stick())
&apos;POC!&apos;
&amp;gt;&amp;gt;&amp;gt; play(SnareDrum(), Brushes())
&apos;SHHHH!&apos;
&amp;gt;&amp;gt;&amp;gt; play(Cymbal(), Brushes())
Traceback (most recent call last):
  File &quot;&amp;lt;stdin&amp;gt;&quot;, line 1, in &amp;lt;module&amp;gt;
  File &quot;/home/jd/Source/cpython/Lib/functools.py&quot;, line 562, in wrapper
    return dispatch(args[0].__class__)(*args, **kw)
  File &quot;/home/jd/sd.py&quot;, line 10, in play
    raise NotImplementedError(&quot;Cannot play these&quot;)
NotImplementedError: Cannot play these
&amp;gt;&amp;gt;&amp;gt; play(SnareDrum(), Cymbal())
Traceback (most recent call last):
  File &quot;&amp;lt;stdin&amp;gt;&quot;, line 1, in &amp;lt;module&amp;gt;
  File &quot;/home/jd/Source/cpython/Lib/functools.py&quot;, line 562, in wrapper
    return dispatch(args[0].__class__)(*args, **kw)
  File &quot;/home/jd/sd.py&quot;, line 18, in _
    raise NotImplementedError(&quot;Cannot play these&quot;)
NotImplementedError: Cannot play these
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;singledispatch&lt;/code&gt; module looks through the classes of the first argument passed to the &lt;code&gt;play&lt;/code&gt; function, and calls the right version of it. The first defined version of the &lt;code&gt;play&lt;/code&gt; function is always run for the &lt;code&gt;object&lt;/code&gt; class, so if our instrument is a class that we did not register for, this base function will be called.&lt;/p&gt;
&lt;p&gt;For whose eager to try and use it, the &lt;code&gt;singledispatch&lt;/code&gt; function is &lt;a href=&quot;https://pypi.python.org/pypi/singledispatch/&quot;&gt;provided Python 2.6 to 3.3 through the Python Package Index&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Limitations&lt;/h2&gt;
&lt;p&gt;First, as you noticed in the Lisp version, CLOS provides a multiple dispatcher that can dispatch on the type of &lt;strong&gt;any of the argument&lt;/strong&gt; defined in the method prototype, not only the first one. Unfortunately, Python dispatcher is named &lt;em&gt;singledispatch&lt;/em&gt; for this good reason: it only knows to dispatch on the first argument. Guido van Rossum wrote a short article about the subject that he called &lt;a href=&quot;http://www.artima.com/weblogs/viewpost.jsp?thread=101605&quot;&gt;multimethod&lt;/a&gt; a few years ago.&lt;/p&gt;
&lt;p&gt;Then, there&apos;s no way to call the parent function directly. There&apos;s no equivalent of the &lt;code&gt;(call-next-method)&lt;/code&gt; from Lisp nor the &lt;code&gt;super()&lt;/code&gt; function that allows to do that in Python class system. This means you will have to use various trick to bypass this limitation.&lt;/p&gt;
&lt;p&gt;So while I am really glad that Python is going toward that direction, as it&apos;s a really powerful way to enhance an object system, it really lacks a lot of more advanced features that CLOS provides out of the box.&lt;/p&gt;
&lt;p&gt;Though, improving this could be an interesting challenge. Especially to bring more CLOS power to &lt;a href=&quot;http://hylang.org&quot;&gt;Hy&lt;/a&gt;. :-)&lt;/p&gt;
</content:encoded></item><item><title>OpenStack Ceilometer Havana-3 milestone released</title><link>https://julien.danjou.info/blog/openstack-ceilometer-havana-3-milestone-released/</link><guid isPermaLink="true">https://julien.danjou.info/blog/openstack-ceilometer-havana-3-milestone-released/</guid><description>Last week, the third and last milestone of the Havana development branch of Ceilometer has been released and is now available for testing and download. This means the end of the OpenStack Havana devel</description><pubDate>Tue, 10 Sep 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last week, the third and last milestone of the Havana development branch of Ceilometer has been released and is now available for testing and download. This means the end of the OpenStack &lt;em&gt;Havana&lt;/em&gt; development time is coming, and that the features are now frozen.&lt;/p&gt;
&lt;h2&gt;New features&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/blueprint-1.jpg&quot; alt=&quot;blueprint-1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Eleven blueprints have been implemented as you can see on the &lt;a href=&quot;https://launchpad.net/ceilometer/+milestone/havana-3&quot;&gt;release page&lt;/a&gt;. That&apos;s one more than during Havana-2, but it&apos;s less than was planned initially, though we had a pretty high score considering the size of our contributors team. I&apos;m going to talk through some of them here, that are the most interesting for users.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Our favorite &lt;a href=&quot;https://wiki.openstack.org/wiki/OutreachProgramForWomen&quot;&gt;OPW&lt;/a&gt; intern Terri Yu implemented the long awaited &lt;a href=&quot;https://blueprints.launchpad.net/ceilometer/+spec/api-group-by&quot;&gt;GROUP BY API feature&lt;/a&gt;, that allows to group samples by fields before returning statistics.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Eoghan Glynn (Red Hat) continued his implementation of alarming features, and the &lt;a href=&quot;https://blueprints.launchpad.net/ceilometer/+spec/alarm-audit-api&quot;&gt;audit API&lt;/a&gt; has been merged. A few blueprints related to alarming slipped and will be delayed for RC1, as they have been granted feature freeze exceptions:&lt;br /&gt;
&lt;a href=&quot;https://blueprints.launchpad.net/ceilometer/+spec/alarming-logical-combination&quot;&gt;logical combinations of alarms&lt;/a&gt; and &lt;a href=&quot;https://blueprints.launchpad.net/ceilometer/+spec/alarm-service-partitioner&quot;&gt;alarm service partitioner&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;With the help of Gordon Chung (IBM), I&apos;ve worked on creating a &lt;a href=&quot;https://blueprints.launchpad.net/ceilometer/+spec/count-api-requests&quot;&gt;middleware to meter API requests&lt;/a&gt;. This has been merged into Oslo and is handled by Ceilometer. Gordon added another middleware on top of it to add CADF support for audit.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ceilometer agent compute gained his second inspector to poll for virtual machine, thanks to Alessandro Pilotti (Cloudbase) who implemented &lt;a href=&quot;https://blueprints.launchpad.net/ceilometer/+spec/hyper-v-agent&quot;&gt;the Hyper-V inspector&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ceilometer will be able to meter Neutron bandwidth thanks to eNovance folks that worked on &lt;a href=&quot;https://blueprints.launchpad.net/ceilometer/+spec/ceilometer-quantum-bw-metering&quot;&gt;bandwidth metering blueprint&lt;/a&gt;, both on Ceilometer and Neutron parts. This is also a long awaited feature.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Finally, Ceilometer will be shipped with yet another storage back-end, as Tong Li (IBM) implemented a &lt;a href=&quot;https://blueprints.launchpad.net/ceilometer/+spec/ibm-db2-support&quot;&gt;DB2 driver&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Bug fixes&lt;/h2&gt;
&lt;p&gt;Fifty-six bugs were fixed, though most of them might not interest you so I won&apos;t elaborate too much on that. Go read &lt;a href=&quot;https://launchpad.net/ceilometer/+milestone/havana-3&quot;&gt;the list&lt;/a&gt; if you are curious.&lt;/p&gt;
&lt;h2&gt;Toward our final Havana release&lt;/h2&gt;
&lt;p&gt;With the feature freeze in place, we&apos;re now focusing on fixing bugs and improving documentation. I&apos;ll try to make sure we&apos;ll get there without too much trouble for the 17th October 2013. Stay tuned!&lt;/p&gt;
</content:encoded></item><item><title>Announcing The Hacker&apos;s Guide to Python</title><link>https://julien.danjou.info/blog/announcing-the-hacker-guide-to-python/</link><guid isPermaLink="true">https://julien.danjou.info/blog/announcing-the-hacker-guide-to-python/</guid><description>I&apos;ve been hacking on Python for a lot of years now, on various project. For the last two years, I&apos;ve been heavily involved in OpenStack, which makes an heavy usage of Python.</description><pubDate>Tue, 03 Sep 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve been hacking on Python for a lot of years now, on various project. For the last two years, I&apos;ve been heavily involved in &lt;a href=&quot;http://openstack.org&quot;&gt;OpenStack&lt;/a&gt;, which makes an heavy usage of Python.&lt;/p&gt;
&lt;p&gt;Once you start working with a hundred of hackers, on several software and libraries representing more than half a million source lines of Python, things change. The scalability, testing and deployment problems inherent to a cloud platform meddle with everything in designing components.&lt;/p&gt;
&lt;p&gt;During these two years working on OpenStack development, I&apos;ve learned a lot on Python from astounding Python hackers. From general architecture and design principles to various tips and tricks of the language.&lt;/p&gt;
&lt;p&gt;It seemed to me like a good opportunity to share what I learnt doing so with others so you can benefit from it in other projects. I&apos;ve started working a book, entitled &quot;The Hacker&apos;s Guide to Python&quot;, where I will try to share what I learnt while working with Python.&lt;/p&gt;
&lt;p&gt;The book is still a work in progress at this stage, but if you&apos;d like to get in touch and keep updated on its advancement, you can subscribe in the following form or from the &lt;a href=&quot;https://thehackerguidetopython.com&quot;&gt;book homepage&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>The definitive guide on how to use static, class or abstract methods in Python</title><link>https://julien.danjou.info/blog/guide-python-static-class-abstract-methods/</link><guid isPermaLink="true">https://julien.danjou.info/blog/guide-python-static-class-abstract-methods/</guid><description>Doing code reviews is a great way to discover things that people might struggle to comprehend. While proof-reading OpenStack patches recently, I spotted that people were not using correctly the.</description><pubDate>Thu, 01 Aug 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Doing code reviews is a great way to discover things that people might struggle to comprehend. While proof-reading &lt;a href=&quot;http://review.openstack.org&quot;&gt;OpenStack patches&lt;/a&gt; recently, I spotted that people were not using correctly the various decorators Python provides for methods. So here&apos;s my attempt at providing me a link to send them to in my next code reviews. :-)&lt;/p&gt;
&lt;h2&gt;How methods work in Python&lt;/h2&gt;
&lt;p&gt;A method is a function that is stored as a class attribute. You can declare and access such a function this way:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; class Pizza(object):
...     def __init__(self, size):
...         self.size = size
...     def get_size(self):
...         return self.size
...
&amp;gt;&amp;gt;&amp;gt; Pizza.get_size
&amp;lt;unbound method Pizza.get_size&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What Python tells you here, is that the attribute &lt;em&gt;get_size&lt;/em&gt; of the class &lt;em&gt;Pizza&lt;/em&gt; is a method that is &lt;strong&gt;unbound&lt;/strong&gt;. What does this mean? We&apos;ll know as soon as we&apos;ll try to call it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; Pizza.get_size()
Traceback (most recent call last):
  File &quot;&amp;lt;stdin&amp;gt;&quot;, line 1, in &amp;lt;module&amp;gt;
TypeError: unbound method get_size() must be called with Pizza instance as first argument (got nothing instead)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can&apos;t call it because it&apos;s not bound to any instance of &lt;em&gt;Pizza&lt;/em&gt;. And a method wants an instance as its first argument (in Python 2 it &lt;strong&gt;must&lt;/strong&gt; be an instance of that class; in Python 3 it could be anything). Let&apos;s try to do that then:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; Pizza.get_size(Pizza(42))
42
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It worked! We called the method with an instance as its first argument, so everything&apos;s fine. But you will agree with me if I say this is not a very handy way to call methods; we have to refer to the class each time we want to call a method. And if we don&apos;t know what class is our object, this is not going to work for very long.&lt;/p&gt;
&lt;p&gt;So what Python does for us, is that it binds all the methods from the class &lt;code&gt;Pizza&lt;/code&gt; to any instance of this class. This means that the attribute &lt;code&gt;get_size&lt;/code&gt; of an instance of &lt;code&gt;Pizza&lt;/code&gt; is a bound method: a method for which the first argument will be the instance itself.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; Pizza(42).get_size
&amp;lt;bound method Pizza.get_size of &amp;lt;__main__.Pizza object at 0x7f3138827910&amp;gt;&amp;gt;
&amp;gt;&amp;gt;&amp;gt; Pizza(42).get_size()
42
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As expected, we don&apos;t have to provide any argument to &lt;code&gt;get_size&lt;/code&gt;, since it&apos;s bound, its &lt;code&gt;self&lt;/code&gt; argument is automatically set to our &lt;code&gt;Pizza&lt;/code&gt; instance. Here&apos;s an even better proof of that:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; m = Pizza(42).get_size
&amp;gt;&amp;gt;&amp;gt; m()
42
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Indeed, you don&apos;t even have to keep a reference to your &lt;code&gt;Pizza&lt;/code&gt; object. Its method is bound to the object, so the method is sufficient to itself.&lt;/p&gt;
&lt;p&gt;But what if you wanted to know which object this bound method is bound to? Here&apos;s a little trick:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; m = Pizza(42).get_size
&amp;gt;&amp;gt;&amp;gt; m.__self__
&amp;lt;__main__.Pizza object at 0x7f3138827910&amp;gt;
&amp;gt;&amp;gt;&amp;gt; # You could guess, look at this:
...
&amp;gt;&amp;gt;&amp;gt; m == m.__self__.get_size
True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Obviously, we still have a reference to our object, and we can find it back if we want.&lt;/p&gt;
&lt;p&gt;In Python 3, the functions attached to a class are not considered as &lt;em&gt;unbound method&lt;/em&gt; anymore, but as simple functions, that are bound to an object if required. So the principle stays the same, the model is just simplified.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; class Pizza(object):
...     def __init__(self, size):
...         self.size = size
...     def get_size(self):
...         return self.size
...
&amp;gt;&amp;gt;&amp;gt; Pizza.get_size
&amp;lt;function Pizza.get_size at 0x7f307f984dd0&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Static methods&lt;/h2&gt;
&lt;p&gt;Static methods are a special case of methods. Sometimes, you&apos;ll write code that belongs to a class, but that doesn&apos;t use the object itself at all. For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Pizza(object):
    @staticmethod
    def mix_ingredients(x, y):
        return x + y

    def cook(self):
        return self.mix_ingredients(self.cheese, self.vegetables)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In such a case, writing &lt;code&gt;mix_ingredients&lt;/code&gt; as a non-static method would work too, but it would provide it with a &lt;code&gt;self&lt;/code&gt; argument that would not be used. Here, the decorator &lt;code&gt;@staticmethod&lt;/code&gt; buys us several things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python doesn&apos;t have to instantiate a bound-method for each &lt;code&gt;Pizza&lt;/code&gt; object we instantiate. Bound methods are objects too, and creating them has a cost. Having a static method avoids that:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; Pizza().cook is Pizza().cook
False
&amp;gt;&amp;gt;&amp;gt; Pizza().mix_ingredients is Pizza.mix_ingredients
True
&amp;gt;&amp;gt;&amp;gt; Pizza().mix_ingredients is Pizza().mix_ingredients
True
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;It eases the readability of the code: seeing &lt;code&gt;@staticmethod&lt;/code&gt;, we know that the method does not depend on the state of the object itself;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It allows us to override the &lt;code&gt;mix_ingredients&lt;/code&gt; method in a subclass. If we used a function &lt;code&gt;mix_ingredients&lt;/code&gt; defined at the top-level of our module, a class inheriting from &lt;code&gt;Pizza&lt;/code&gt; wouldn&apos;t be able to change the way we mix ingredients for our pizza without overriding &lt;code&gt;cook&lt;/code&gt; itself.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Class methods&lt;/h2&gt;
&lt;p&gt;Having said that, what are class methods? Class methods are methods that are&lt;br /&gt;
not bound to an object, but to… a class!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; class Pizza(object):
...     radius = 42
...     @classmethod
...     def get_radius(cls):
...         return cls.radius
... 
&amp;gt;&amp;gt;&amp;gt; 
&amp;gt;&amp;gt;&amp;gt; Pizza.get_radius
&amp;lt;bound method type.get_radius of &amp;lt;class &apos;__main__.Pizza&apos;&amp;gt;&amp;gt;
&amp;gt;&amp;gt;&amp;gt; Pizza().get_radius
&amp;lt;bound method type.get_radius of &amp;lt;class &apos;__main__.Pizza&apos;&amp;gt;&amp;gt;
&amp;gt;&amp;gt;&amp;gt; Pizza.get_radius == Pizza().get_radius
True
&amp;gt;&amp;gt;&amp;gt; Pizza.get_radius()
42
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whatever the way you use to access this method, it will always be bound to the class it is attached to, and its first argument will be the class itself (remember that classes are objects too).&lt;/p&gt;
&lt;p&gt;When to use this kind of methods? Well class methods are mostly useful for two types of methods:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Factory methods, that are used to create an instance for a class using for example some sort of pre-processing. If we use a &lt;code&gt;@staticmethod&lt;/code&gt; instead, we would have to hardcode the &lt;code&gt;Pizza&lt;/code&gt; class name in our function, making any class inheriting from &lt;code&gt;Pizza&lt;/code&gt; unable to use our factory for its own use.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;class Pizza(object):
    def __init__(self, ingredients):
        self.ingredients = ingredients

    @classmethod
    def from_fridge(cls, fridge):
        return cls(fridge.get_cheese() + fridge.get_vegetables())
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Static methods calling static methods: if you split a static method in several static methods, you shouldn&apos;t hard-code the class name but use class methods. Using this way to declare our method, the &lt;code&gt;Pizza&lt;/code&gt; name is never directly referenced and inheritance and method overriding will work flawlessly&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;class Pizza(object):
    def __init__(self, radius, height):
        self.radius = radius
        self.height = height

    @staticmethod
    def compute_area(radius):
         return math.pi * (radius ** 2)

    @classmethod
    def compute_volume(cls, height, radius):
         return height * cls.compute_area(radius)

    def get_volume(self):
        return self.compute_volume(self.height, self.radius)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Abstract methods&lt;/h2&gt;
&lt;p&gt;An abstract method is a method defined in a base class, but that may not provide any implementation. In Java, it would describe the methods of an interface.&lt;/p&gt;
&lt;p&gt;So the simplest way to write an abstract method in Python is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Pizza(object):
    def get_radius(self):
        raise NotImplementedError
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Any class inheriting from &lt;code&gt;Pizza&lt;/code&gt; should implement and override the &lt;code&gt;get_radius&lt;/code&gt; method, otherwise an exception would be raised.&lt;/p&gt;
&lt;p&gt;This particular way of implementing abstract method has a drawback. If you write a class that inherits from &lt;code&gt;Pizza&lt;/code&gt; and forget to implement &lt;code&gt;get_radius&lt;/code&gt;, the error will only be raised when you&apos;ll try to use that method.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; Pizza()
&amp;lt;__main__.Pizza object at 0x7fb747353d90&amp;gt;
&amp;gt;&amp;gt;&amp;gt; Pizza().get_radius()
Traceback (most recent call last):
  File &quot;&amp;lt;stdin&amp;gt;&quot;, line 1, in &amp;lt;module&amp;gt;
  File &quot;&amp;lt;stdin&amp;gt;&quot;, line 3, in get_radius
NotImplementedError
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There&apos;s a way to trigger this way earlier, when the object is being instantiated, using the &lt;a href=&quot;http://docs.python.org/2/library/abc.html&quot;&gt;abc&lt;/a&gt; module that&apos;s provided with Python.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import abc

class BasePizza(object):
    __metaclass__  = abc.ABCMeta

    @abc.abstractmethod
    def get_radius(self):
         &quot;&quot;&quot;Method that should do something.&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using &lt;code&gt;abc&lt;/code&gt; and its special class, as soon as you&apos;ll try to instantiate &lt;code&gt;BasePizza&lt;/code&gt; or any class inheriting from it, you&apos;ll get a &lt;code&gt;TypeError&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; BasePizza()
Traceback (most recent call last):
  File &quot;&amp;lt;stdin&amp;gt;&quot;, line 1, in &amp;lt;module&amp;gt;
TypeError: Can&apos;t instantiate abstract class BasePizza with abstract methods get_radius
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Mixing static, class and abstract methods&lt;/h2&gt;
&lt;p&gt;When building classes and inheritances, the time will come where you will have to mix all these methods decorators. So here&apos;s some tips about it.&lt;/p&gt;
&lt;p&gt;Keep in mind that declaring a method as being abstract, doesn&apos;t freeze the prototype of that method. That means that it must be implemented, but it can be implemented with any argument list.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import abc

class BasePizza(object):
    __metaclass__  = abc.ABCMeta

    @abc.abstractmethod
    def get_ingredients(self):
         &quot;&quot;&quot;Returns the ingredient list.&quot;&quot;&quot;

class Calzone(BasePizza):
    def get_ingredients(self, with_egg=False):
        egg = Egg() if with_egg else None
        return self.ingredients + egg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is valid, since &lt;code&gt;Calzone&lt;/code&gt; fulfills the interface requirement we defined for &lt;code&gt;BasePizza&lt;/code&gt; objects. That means that we could also implement it as being a class or a static method, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import abc

class BasePizza(object):
    __metaclass__  = abc.ABCMeta

    @abc.abstractmethod
    def get_ingredients(self):
         &quot;&quot;&quot;Returns the ingredient list.&quot;&quot;&quot;

class DietPizza(BasePizza):
    @staticmethod
    def get_ingredients():
        return None
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is also correct and fulfills the contract we have with our abstract &lt;code&gt;BasePizza&lt;/code&gt; class. The fact that the &lt;code&gt;get_ingredients&lt;/code&gt; method doesn&apos;t need to know about the object to return result is an implementation detail, not a criteria to have our contract fulfilled.&lt;/p&gt;
&lt;p&gt;Therefore, you can&apos;t force an implementation of your abstract method to be a regular, class or static method, and arguably you shouldn&apos;t. Starting with Python 3 (this won&apos;t work as you would expect in Python 2, see &lt;a href=&quot;http://bugs.python.org/issue5867&quot;&gt;issue5867&lt;/a&gt;), it&apos;s now possible to use the &lt;code&gt;@staticmethod&lt;/code&gt; and &lt;code&gt;@classmethod&lt;/code&gt; decorators on top of &lt;code&gt;@abstractmethod&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import abc

class BasePizza(object):
    __metaclass__  = abc.ABCMeta

    ingredient = [&apos;cheese&apos;]

    @classmethod
    @abc.abstractmethod
    def get_ingredients(cls):
         &quot;&quot;&quot;Returns the ingredient list.&quot;&quot;&quot;
         return cls.ingredients
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Don&apos;t misread this: if you think this is going to force your subclasses to implement &lt;code&gt;get_ingredients&lt;/code&gt; as a class method, you are wrong. This simply implies that your implementation of &lt;code&gt;get_ingredients&lt;/code&gt; in the &lt;code&gt;BasePizza&lt;/code&gt; class is a class method.&lt;/p&gt;
&lt;p&gt;An implementation in an abstract method? Yes! In Python, contrary to methods in Java interfaces, you can have code in your abstract methods and call it via &lt;code&gt;super()&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import abc

class BasePizza(object):
    __metaclass__  = abc.ABCMeta

    default_ingredients = [&apos;cheese&apos;]

    @classmethod
    @abc.abstractmethod
    def get_ingredients(cls):
         &quot;&quot;&quot;Returns the ingredient list.&quot;&quot;&quot;
         return cls.default_ingredients

class DietPizza(BasePizza):
    def get_ingredients(self):
        return [&apos;egg&apos;] + super(DietPizza, self).get_ingredients()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In such a case, every pizza you will build by inheriting from &lt;code&gt;BasePizza&lt;/code&gt; will have to override the &lt;code&gt;get_ingredients&lt;/code&gt; method, but will be able to use the default mechanism to get the ingredient list by using &lt;code&gt;super()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you&apos;re interested in knowing more, I&apos;ve covered this topic extensively in &lt;a href=&quot;https://thehackerguidetopython.com&quot;&gt;The Hacker&apos;s Guide to Python&lt;/a&gt;. Checkout it out!&lt;/p&gt;
</content:encoded></item><item><title>OpenStack Ceilometer Havana-2 milestone released</title><link>https://julien.danjou.info/blog/openstack-ceilometer-havana-2-milestone-released/</link><guid isPermaLink="true">https://julien.danjou.info/blog/openstack-ceilometer-havana-2-milestone-released/</guid><description>Last week, the second milestone of the Havana development branch of Ceilometer has been released and is now available for testing and download. This means the first half of the OpenStack Havana develo</description><pubDate>Sat, 27 Jul 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last week, the second milestone of the Havana development branch of Ceilometer has been released and is now available for testing and download. This means the first half of the OpenStack &lt;em&gt;Havana&lt;/em&gt; development has passed!&lt;/p&gt;
&lt;h2&gt;New features&lt;/h2&gt;
&lt;p&gt;Ten blueprints have been implemented as you can see on the &lt;a href=&quot;https://launchpad.net/ceilometer/+milestone/havana-2&quot;&gt;release page&lt;/a&gt;. I&apos;m going to talk through some of them here, that are the most interesting for users.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/blueprint.jpg&quot; alt=&quot;blueprint&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The Ceilometer API now returns &lt;a href=&quot;https://blueprints.launchpad.net/ceilometer/+spec/api-sample-sorted&quot;&gt;all the samples sorted by timestamp&lt;/a&gt;. This blueprint is the first one implemented by Terri Yu, our &lt;a href=&quot;https://wiki.openstack.org/wiki/OutreachProgramForWomen&quot;&gt;OPW&lt;/a&gt; intern! In the same spirit, I&apos;ve added the ability to &lt;a href=&quot;https://blueprints.launchpad.net/ceilometer/+spec/api-limit&quot;&gt;limit the number of samples returned&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;On the alarming front, things evolved a lot. I&apos;ve implemented the &lt;a href=&quot;https://blueprints.launchpad.net/ceilometer/+spec/alarm-notifier&quot;&gt;notifier system&lt;/a&gt; that will be used to run actions when alarms are triggered. To trigger these alarms, Eoghan Glynn (Red Hat) worked on the &lt;a href=&quot;https://blueprints.launchpad.net/ceilometer/+spec/alarm-distributed-threshold-evaluation&quot;&gt;alarm evaluation system&lt;/a&gt; that will use the Ceilometer API to check for alarm states.&lt;/p&gt;
&lt;p&gt;I&apos;ve reworked the publisher system so it now uses &lt;a href=&quot;https://blueprints.launchpad.net/ceilometer/+spec/pipeline-publisher-url&quot;&gt;URL formatted target&lt;/a&gt; for publication. That now allows to publish different meters to different target using the same publishing protocol (e.g. via UDP toward different hosts).&lt;/p&gt;
&lt;p&gt;Sandy Walsh (RackSpace) have been working on the StackTach like functionality and added the ability for the collector to optionally &lt;a href=&quot;https://blueprints.launchpad.net/ceilometer/+spec/collector-stores-events&quot;&gt;store the notification events received&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Finally, Mehdi Abaakouk (eNovance) implemented a &lt;a href=&quot;https://blueprints.launchpad.net/ceilometer/+spec/db-ttl&quot;&gt;TTL system for the database&lt;/a&gt;, so you&apos;re now able to expire your data whenever you like.&lt;/p&gt;
&lt;h2&gt;Bug fixes&lt;/h2&gt;
&lt;p&gt;Thirty-five bugs were fixed, though most of them might not interest you so I won&apos;t elaborate too much on that. Go read &lt;a href=&quot;https://launchpad.net/ceilometer/+milestone/havana-2&quot;&gt;the list&lt;/a&gt; if you are curious.&lt;/p&gt;
&lt;h2&gt;Toward Havana 3&lt;/h2&gt;
&lt;p&gt;We now have 30 blueprints targeting the &lt;a href=&quot;https://launchpad.net/ceilometer/+milestone/havana-3&quot;&gt;Ceilometer&apos;s third Havana milestone&lt;/a&gt;, with some of them are already started. I&apos;ll try to make sure we&apos;ll get there without too much trouble for the 6th September 2013. Stay tuned!&lt;/p&gt;
</content:encoded></item><item><title>OpenStack meets Lisp: cl-openstack-client</title><link>https://julien.danjou.info/blog/lisp-and-openstack-with-cl-openstack-client/</link><guid isPermaLink="true">https://julien.danjou.info/blog/lisp-and-openstack-with-cl-openstack-client/</guid><description>Building an OpenStack client library in Common Lisp, exploring what it takes to bring the OpenStack community beyond Python.</description><pubDate>Thu, 04 Jul 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A month ago, a mail hit the &lt;a href=&quot;http://openstack.org&quot;&gt;OpenStack&lt;/a&gt; mailing list entitled &quot;&lt;a href=&quot;https://lists.launchpad.net/openstack/msg24349.html&quot;&gt;The OpenStack Community Welcomes Developers in All Programming Languages&lt;/a&gt;&quot;. You may know that OpenStack is essentially built using Python, and therefore it is the reference language for the client libraries implementations. As a Lisp and OpenStack practitioner, I used this excuse to build a challenge for myself: let&apos;s prove this point by bringing Lisp into OpenStack!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/cl-openstack-client-1.png&quot; alt=&quot;cl-openstack-client-1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Welcome &lt;a href=&quot;https://github.com/stackforge/cl-openstack-client&quot;&gt;cl-openstack-client&lt;/a&gt;, the OpenStack client library for &lt;a href=&quot;http://common-lisp.net/&quot;&gt;Common Lisp&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;The project is hosted on the classic OpenStack infrastructure for third party project, &lt;a href=&quot;http://ci.openstack.org/stackforge.html&quot;&gt;StackForge&lt;/a&gt;. It provides the &lt;a href=&quot;https://jenkins.openstack.org/job/gate-cl-openstack-client-run-tests/&quot;&gt;continuous integration system based on Jenkins&lt;/a&gt; and the Gerrit infrastructure used to review contributions.&lt;/p&gt;
&lt;h2&gt;How the tests works&lt;/h2&gt;
&lt;p&gt;OpenStack projects ran a fabulous contribution workflow, &lt;a href=&quot;https://julien.danjou.info/blog/2013/rant-about-github-pull-request-workflow-implementation&quot;&gt;which I already talked about&lt;/a&gt;, based on tools like &lt;a href=&quot;http://gerrit.googlecode.com/&quot;&gt;Gerrit&lt;/a&gt; and &lt;a href=&quot;http://jenkins-ci.org/&quot;&gt;Jenkins&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;OpenStack Python projects are used to run &lt;a href=&quot;https://pypi.python.org/pypi/tox&quot;&gt;tox&lt;/a&gt;, to build a virtual environment and run test inside. We don&apos;t have such thing in Common Lisp as far as I know, so I had to build it myself.&lt;/p&gt;
&lt;p&gt;Fortunately, using &lt;a href=&quot;http://www.quicklisp.org/&quot;&gt;Quicklisp&lt;/a&gt;, the fabulous equivalent of Python&apos;s PyPI, it has been a breeze to set this up. &lt;em&gt;cl-openstack-client&lt;/em&gt; just includes a &lt;a href=&quot;https://github.com/stackforge/cl-openstack-client/blob/master/run-tests.sh&quot;&gt;basic shell script&lt;/a&gt; that does the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Download quicklisp.lisp&lt;/li&gt;
&lt;li&gt;Run a &lt;a href=&quot;https://github.com/stackforge/cl-openstack-client/blob/master/update-deps.lisp&quot;&gt;Lisp program to install the dependencies using Quicklisp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Run a &lt;a href=&quot;https://github.com/stackforge/cl-openstack-client/blob/master/run-tests.lisp&quot;&gt;Lisp program running the test suite&lt;/a&gt; using &lt;a href=&quot;http://common-lisp.net/project/fiveam/&quot;&gt;FiveAM&lt;/a&gt;, that exit with 0 or 1 based on the tests results.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I just run the test using &lt;a href=&quot;http://www.sbcl.org&quot;&gt;SBCL&lt;/a&gt;, though adding more compiler on the table would be a really good plan in the future, and should be straightforward. You can &lt;a href=&quot;https://jenkins.openstack.org/job/gate-cl-openstack-client-run-tests/4/console&quot;&gt;admire a log from a successful test&lt;/a&gt; run done when I proposed a patch via Gerrit, to check what it looks like.&lt;/p&gt;
&lt;h2&gt;Implementation status&lt;/h2&gt;
&lt;p&gt;For the curious, here&apos;s an example of how it works:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;* (require &apos;cl-openstack-client)
* (use-package &apos;cl-keystone-client)
* (defvar k (make-instance &apos;connection-v2 :username &quot;demo&quot; :password &quot;somepassword&quot; :tenant-name &quot;demo&quot; :url &quot;http://devstack:5000&quot;))

K

* (authenticate k)

((:ISSUED--AT . &quot;2013-07-04T05:59:55.454226&quot;)
 (:EXPIRES . &quot;2013-07-05T05:59:55Z&quot;)
 (:ID
  . &quot;wNFQwNzo1OTo1NS40NTQyMthisisaverylongtokenwNFQwNzo1OTo1NS40NTQyM&quot;)
 (:TENANT (:DESCRIPTION) (:ENABLED . T)
  (:ID . &quot;1774fd545df4400380eb2b4f4985b3be&quot;) (:NAME . &quot;demo&quot;)))

* (connection-token-id k)

&quot;wNFQwNzo1OTo1NS40NTQyMthisisaverylongtokenwNFQwNzo1OTo1NS40NTQyM&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Unfortunately, the implementation is far from being complete. It only implements for now the Keystone token retrieval.&lt;/p&gt;
&lt;p&gt;I&apos;ve actually started this project to build an already working starting point. With this, future potential contributors will be able to spend efforts on writing code, and not on setting up the basic continuous integration system or module infrastructure.&lt;/p&gt;
&lt;p&gt;If you wish to help me and contribute, just follow the &lt;a href=&quot;https://wiki.openstack.org/wiki/GerritWorkflow&quot;&gt;OpenStack Gerrit workflow howto&lt;/a&gt; or feel free to come by me and ask any question (I&apos;m hanging out on #lisp on Freenode too).&lt;/p&gt;
&lt;p&gt;See you soon, hopping to bring more Lisp into OpenStack!&lt;/p&gt;
</content:encoded></item><item><title>OpenStack Ceilometer Havana-1 milestone released</title><link>https://julien.danjou.info/blog/openstack-ceilometer-havana-1-milestone-released/</link><guid isPermaLink="true">https://julien.danjou.info/blog/openstack-ceilometer-havana-1-milestone-released/</guid><description>Yesterday, the first milestone of the Havana development branch of Ceilometer has been released and is now available for testing and download. This means the first quarter of the OpenStack Havana deve</description><pubDate>Fri, 31 May 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Yesterday, the first milestone of the Havana development branch of Ceilometer has been released and is now available for testing and download. This means the first quarter of the OpenStack &lt;em&gt;Havana&lt;/em&gt; development has passed!&lt;/p&gt;
&lt;h2&gt;New features&lt;/h2&gt;
&lt;p&gt;Ten blueprints have been implemented as you can see on the &lt;a href=&quot;https://launchpad.net/ceilometer/+milestone/havana-1&quot;&gt;release page&lt;/a&gt;. I&apos;m going to talk through some of them here, that are the most interesting for users.&lt;/p&gt;
&lt;p&gt;Ceilometer can now &lt;a href=&quot;https://blueprints.launchpad.net/ceilometer/+spec/scheduler-counter&quot;&gt;counts the scheduling attempt&lt;/a&gt; of instances done by &lt;em&gt;nova-scheduler&lt;/em&gt;. This can be useful to eventually bill such information or for audit (implemented by me for eNovance).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/hbase.png&quot; alt=&quot;hbase&quot; /&gt;&lt;/p&gt;
&lt;p&gt;People using the &lt;a href=&quot;http://hbase.apache.org/&quot;&gt;HBase&lt;/a&gt; backend can now do requests filtering on any of the counter fields, something we call &lt;a href=&quot;https://blueprints.launchpad.net/ceilometer/+spec/hbase-metadata-query&quot;&gt;metadata queries&lt;/a&gt;, and which was missing for this backend driver. Thanks to Shengjie Min (Dell) for the implementation.&lt;/p&gt;
&lt;p&gt;Counters can now be &lt;a href=&quot;https://blueprints.launchpad.net/ceilometer/+spec/udp-publishing&quot;&gt;sent over UDP&lt;/a&gt; instead of the Oslo RPC mechanism (AMQP based by default). This allows counter transmission to be done in a much faster way, though less reliable. The primary use case being not audit or billing, but the alarming features that we are working on (implemented by me for eNovance).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/siren.png&quot; alt=&quot;siren&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://blueprints.launchpad.net/ceilometer/+spec/alarm-api&quot;&gt;initial alarm API&lt;/a&gt; has been designed and implemented, thanks to Mehdi Abaakouk (eNovance) and Angus Salkled (RedHat) who tackled this. We&apos;re now able to do &lt;em&gt;CRUD&lt;/em&gt; actions on these.&lt;/p&gt;
&lt;p&gt;Posting of meters via the HTTP API is now possible. This is now another conduct that can be used to publish and collector meter. Thanks to Angus Salkled (RedHat) for implementing this.&lt;/p&gt;
&lt;p&gt;I&apos;ve been working on an somewhat experimental &lt;a href=&quot;https://blueprints.launchpad.net/ceilometer/+spec/oslo-multi-publisher&quot;&gt;notifier driver for Oslo&lt;/a&gt; notification that publishes Ceilometer counters instead of the standard notification, using the Ceilometer pipeline setup.&lt;/p&gt;
&lt;p&gt;Sandy Walsh (Rackspace) has put in place the base needed to &lt;a href=&quot;https://blueprints.launchpad.net/ceilometer/+spec/add-event-table&quot;&gt;store raw notifications (events)&lt;/a&gt;, with the final goal of bringing more functionalities around these into Ceilometer.&lt;/p&gt;
&lt;p&gt;Obviously, all of this blueprint and bug fixes wouldn&apos;t be implemented or fixed without the harden eyes of our entire team, reviewing code and advising restlessly the developers. Thanks to them!&lt;/p&gt;
&lt;h2&gt;Bug fixes&lt;/h2&gt;
&lt;p&gt;Thirty-one bugs were fixed, though most of them might not interest you so I won&apos;t elaborate too much on that. Go read &lt;a href=&quot;https://launchpad.net/ceilometer/+milestone/havana-1&quot;&gt;the list&lt;/a&gt; if you are curious.&lt;/p&gt;
&lt;h2&gt;Toward Havana 2&lt;/h2&gt;
&lt;p&gt;We now have 21 blueprints targeting the &lt;a href=&quot;https://launchpad.net/ceilometer/+milestone/havana-2&quot;&gt;Ceilometer&apos;s second Havana milestone&lt;/a&gt;, with some of them are already started. I&apos;ll try to make sure we&apos;ll get there without too much trouble for the 18th July 2013. Stay tuned!&lt;/p&gt;
</content:encoded></item><item><title>Rant about Github pull-request workflow implementation</title><link>https://julien.danjou.info/blog/rant-about-github-pull-request-workflow-implementation/</link><guid isPermaLink="true">https://julien.danjou.info/blog/rant-about-github-pull-request-workflow-implementation/</guid><description>One of my recent innocent tweet about Gerrit vs Github triggered much more reponses and debate that I expected it to. I realize that it might be worth explaining a bit what I meant, in a text longer t</description><pubDate>Fri, 10 May 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;One of my recent innocent tweet about &lt;em&gt;Gerrit vs Github&lt;/em&gt; triggered much more reponses and debate that I expected it to. I realize that it might be worth explaining a bit what I meant, in a text longer than 140 characters.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I&apos;m having a hard time now contributing to projects not using Gerrit. Github isn&apos;t that good.&lt;/p&gt;
&lt;p&gt;— Julien Danjou (@juldanjou) &lt;a href=&quot;https://twitter.com/juldanjou/status/332076595521146881&quot;&gt;May 8, 2013&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;The problems with Github pull-requests&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/github-1.svg&quot; alt=&quot;github-1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I always looked at Github from a distant eye, mainly because I always disliked their pull-request handling, and saw no value in the social hype it brings. Why?&lt;/p&gt;
&lt;h3&gt;One click away isn&apos;t one click effort&lt;/h3&gt;
&lt;p&gt;The pull-request system looks like an incredible easy way to contribute to any project hosted on Github. You&apos;re a click away to send your contribution to any software. But the problem is that any worthy contribution isn&apos;t an effort of a single click.&lt;/p&gt;
&lt;p&gt;Doing any proper and useful contribution to a software is never done right the first time. There&apos;s a dance you will have to play. A slowly rhythmed back and forth between you and the software maintainer or team. You&apos;ll have to dance it until your contribution is correct and can be merged.&lt;/p&gt;
&lt;p&gt;But as a software maintainer, not everybody is going to follow you on this choregraphy, and you&apos;ll end up with pull-request you&apos;ll never get finished unless you wrap things up yourself. So the gain in pull-requests here, isn&apos;t really bigger than a good bug report in most cases.&lt;/p&gt;
&lt;p&gt;This is where the social argument of Github isn&apos;t anymore. As soon as you&apos;re talking about projects bigger than a color theme for your favorite text editor, this feature is overrated.&lt;/p&gt;
&lt;h3&gt;Contribution rework&lt;/h3&gt;
&lt;p&gt;If you&apos;re lucky enough, your contributor will play along and follow you on this pull-request review process. You&apos;ll make suggestions, he will listen and will modify his pull-request to follow your advice.&lt;/p&gt;
&lt;p&gt;At this point, there&apos;s two technics he can use to please you.&lt;/p&gt;
&lt;h4&gt;Technic #1: the Topping&lt;/h4&gt;
&lt;p&gt;Github&apos;s pull-requests invite you to send an entire branch, eclipsing the fact that it is composed of several commits. The problem is that a lot of one-click-away contributors do not masterize Git and/or do not make efforts to build a logical patchset, and nothing warns them that their branch history is wrong. So they tend to change stuff around, commit, make a mistake, commit, fix this mistake, commit, etc. This kind of branch is composed of the whole brain&apos;s construction process of your contributor, and is a real pain to review. To the point I quite often give up.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/github-pull-request-iterative.png&quot; alt=&quot;github-pull-request-iterative&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Without Github, the old method that all software used, and that many software still use (e.g. Linux), is to send a patch set over e-mail (or any other medium like Gerrit). This method has one positive effect, that it forces the contributor to acknowledge the list of commits he is going to publish. So, if the contributor he has fixup commits in his history, they are going to be seen as first class citizen. And nobody is going to want to see that, neither your contributor, nor the software maintainers. Therefore, such a system tend to push contributors to write atomic, logical and self-contained patchset that can be more easily reviewed.&lt;/p&gt;
&lt;h4&gt;Technic #2: the History Rewriter&lt;/h4&gt;
&lt;p&gt;This is actually the good way to build a working and logical patchset using Git. Rewriting history and amending problematic patches using the famous &lt;code&gt;git rebase --interactive&lt;/code&gt; trick.&lt;/p&gt;
&lt;p&gt;The problem is that if your contributor does this and then repush the branch composing your pull-request to Github, you will both lose the previous review done, each time. There&apos;s no history on the different versions of the branch that has been pushed.&lt;/p&gt;
&lt;p&gt;In the old alternative system like e-mail, no information is lost when reworked patches are resent, obviously. This is far better because it eases the following of the iterative discussions that the patch triggered.&lt;/p&gt;
&lt;p&gt;Of course, it would be possible for Github to enhance this and fix it, but currently it doesn&apos;t handle this use case correctly..&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/hylang-pull-request-157.png&quot; alt=&quot;Exercise for the doubtful readers: good luck finding all revisions of my patch in the pull-request #157 of Hy.&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;A quick look at OpenStack workflow&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/openstack-5.png&quot; alt=&quot;openstack-5&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It&apos;s not a secret for anyone that I&apos;ve been contributing to OpenStack as a daily routine for the last 18 months. The more I contribute, the more I like the contribution workflow and process. It&apos;s already &lt;a href=&quot;https://wiki.openstack.org/wiki/Gerrit_Workflow&quot;&gt;well and longly described on the wiki&lt;/a&gt;, so I&apos;ll summarize here my view and what I like about it.&lt;/p&gt;
&lt;h3&gt;Gerrit&lt;/h3&gt;
&lt;p&gt;To send a contribution to any OpenStack project, you need to pass via Gerrit. This is way simpler than doing a pull-request on Github actually, all you have to do is do your commit(s), and type &lt;a href=&quot;https://pypi.python.org/pypi/git-review&quot;&gt;&lt;code&gt;git review&lt;/code&gt;&lt;/a&gt;. That&apos;s it. Your patch will be pushed to Gerrit and available for review.&lt;/p&gt;
&lt;p&gt;Gerrit allows other developers to review your patch, add comments anywhere on it, and score your patch up or down. You can build any rule you want for the score needed for a patch to be merged; OpenStack requires one positive scoring from two core developers before the patch is merged.&lt;/p&gt;
&lt;p&gt;Until a patch is validated, it can be reworked and amended locally using Git, and then resent using &lt;code&gt;git review&lt;/code&gt; again. That simple. The historic and the different version of the patches are available, with the whole comments. Gerrit doesn&apos;t lose any historic information on your workflow.&lt;/p&gt;
&lt;p&gt;Finally, you&apos;ll notice that this is actually the same kind of workflow projects use when they work by patch sent over e-mail. Gerrit just build a single place to regroup and keep track of patchsets, which is really handy. It&apos;s also much easier for people to actually send patch using a command line tool than their MUA or &lt;em&gt;git send-email&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;Gate testing&lt;/h3&gt;
&lt;p&gt;Testing is mandatory for any patch sent to OpenStack. Unit tests and functionnals test are run for &lt;em&gt;each version of each patch of the patchset&lt;/em&gt; sent. And until your patch passes all tests, it will be &lt;em&gt;impossible&lt;/em&gt; to merge it.&lt;/p&gt;
&lt;p&gt;Yes, this implies that all patches in a patchset must be working commits and can be merged on their own, without the entire patchset going in! With such a restricution, it&apos;s impossible to have &quot;fixup commits&quot; merged in your project and pollute the history and the testability of the project.&lt;/p&gt;
&lt;p&gt;Once your patch is validated by core developers, the system checks that there is not any merge conflicts. If there&apos;s not, tests are re-run, since the branch you are pushing to might have changed, and if everything&apos;s fine, the patch is merged.&lt;/p&gt;
&lt;p&gt;This is an uncredible force for the quality of the project. This implies that no broken patchset can ever sneak in, and that the project pass always all tests.&lt;/p&gt;
&lt;h2&gt;Conclusion: accessibility vs code review&lt;/h2&gt;
&lt;p&gt;In the end, I think that one of the key of any development process, which is code review, is not well covered by Github pull-request system. It is, along with history integrity, damaged by the goal of making contributions easier.&lt;/p&gt;
&lt;p&gt;Choosing between these features is probably a trade-off that each project should do carefully, considering what are its core goals and the quality of code it want to reach.&lt;/p&gt;
&lt;p&gt;I tend to find that OpenStack found one of the best trade-off available using Gerrit and plugging testing automation via Jenkins on it, and I would probably recommend it for any project taking seriously code reviews and testing.&lt;/p&gt;
</content:encoded></item><item><title>OpenStack Design Summit Havana, from a Ceilometer point of view</title><link>https://julien.danjou.info/blog/openstack-summit-havana-ceilometer/</link><guid isPermaLink="true">https://julien.danjou.info/blog/openstack-summit-havana-ceilometer/</guid><description>Last week was the OpenStack Design Summit in Portland, OR where we, developers, discussed and designed the new OpenStack release (Havana) coming up.</description><pubDate>Thu, 25 Apr 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last week was the &lt;a href=&quot;https://www.openstack.org/summit/portland-2013/&quot;&gt;OpenStack Design Summit&lt;/a&gt; in Portland, OR where we, developers, discussed and designed the new OpenStack release (Havana) coming up.&lt;/p&gt;
&lt;p&gt;The summit has been wonderful. It was my first OpenStack design summit -- even more as a PTL -- and bumping into various people I&apos;ve never met so far and worked with online only was a real pleasure!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/ods_havana_ceilometer_nijaba_jd_talk.jpg&quot; alt=&quot;ods_havana_ceilometer_nijaba_jd_talk&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://nicolas.barcet.com/&quot;&gt;Nick Barcet&lt;/a&gt; from &lt;a href=&quot;http://www.enovance.com&quot;&gt;eNovance&lt;/a&gt;, our dear previous Ceilometer PTL, and myself, talked about Ceilometer and presented the work that has been done for Grizzly, with some previews of what we&apos;ll like to see done for its Havana release.&lt;/p&gt;
&lt;h2&gt;Design sessions&lt;/h2&gt;
&lt;p&gt;Ceilometer had his design sessions during the last days of the summit. We noted a lot of things and commented during the sessions in our &lt;a href=&quot;https://wiki.openstack.org/wiki/Summit/Havana/Etherpads#Ceilometer&quot;&gt;Etherpads instances&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The first session was a description of Ceilometer core architecture for interested people, and was a wonderful success considering that the room was packed. Our &lt;a href=&quot;http://doughellmann.com/&quot;&gt;Doug Hellmann&lt;/a&gt; did a wonderful job introducing people to Ceilometer and answering question.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/ods_havana_ceilometer_dhellmann.jpg&quot; alt=&quot;ods_havana_ceilometer_dhellmann&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The next session was about getting feedbacks from our users. We had a lot of surprise to discover wonderful real use-cases and deployments, like the CERN using Ceilometer and generating 2 GB of data per day!&lt;/p&gt;
&lt;p&gt;The following sessions ran on Thursday and were much more about new features discussion. A lot ot already existing blueprints were discussed and quickly validated during the first morning session. Then, &lt;a href=&quot;http://www.sandywalsh.com/&quot;&gt;Sandy Walsh&lt;/a&gt; introduced the architecture they use inside &lt;a href=&quot;https://github.com/rackerlabs/stacktach&quot;&gt;StackTach&lt;/a&gt;, so we can start thinking about getting things from it into Ceilometer.&lt;/p&gt;
&lt;p&gt;API improvements were discussed without surprises and with a good consensus on what needs to be done. The four following sessions that occupied a lot of the days were related to alarming. All were lead by Eoghan Glynn, from &lt;a href=&quot;http://redhat.com&quot;&gt;Red Hat&lt;/a&gt;, who did an amazing job presenting the possible architectures with theirs pros and cons. Actually, all we had to do was to nod to his designs and acknowledge the plan on how to build this.&lt;/p&gt;
&lt;p&gt;That last two sessions were about discussing advanced models for billing where we got some interesting feedback from Daniel Dyer from HP, and then were a quick follow-up of the StackTach presentation from the morning session.&lt;/p&gt;
&lt;h2&gt;Havana roadmap&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://blueprints.launchpad.net/ceilometer/havana&quot;&gt;list of blueprints targeting Havana is available&lt;/a&gt; and should be finished by next week. If you want to propose blueprints, you&apos;re free to do so and inform us about it so we can validate it. The same applies if you wish to implement one of them!&lt;/p&gt;
&lt;h3&gt;API extension&lt;/h3&gt;
&lt;p&gt;I do think the API version 2 is going to be heavily extended during this release cycle. We need more feature, like the &lt;a href=&quot;https://blueprints.launchpad.net/ceilometer/+spec/api-group-by&quot;&gt;group-by&lt;/a&gt;&lt;br /&gt;
functionality.&lt;/p&gt;
&lt;h3&gt;Healthnmon&lt;/h3&gt;
&lt;p&gt;In parallel of the design sessions, discussions took place in the unconference room with the Healthnmon developers to figure out a plan in order to merge some of their efforts into Ceilometer. They should provide a component to help Ceilometer supports more hypervisors than it currently does.&lt;/p&gt;
&lt;h3&gt;Alarming&lt;/h3&gt;
&lt;p&gt;Alarming is definitely going to be the next big project for Ceilometer. Today, Eoghan and I started building blueprints on alarming, &lt;a href=&quot;https://blueprints.launchpad.net/ceilometer/+spec/alarming&quot;&gt;centralised in a general blueprint&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We know this is going to happen for real and very soon, thanks to the engagements of &lt;a href=&quot;http://enovance.com&quot;&gt;eNovance&lt;/a&gt; and &lt;a href=&quot;http://redhat.com&quot;&gt;Red Hat&lt;/a&gt; who are committing resources to this amazing project!&lt;/p&gt;
</content:encoded></item><item><title>Hy, Lisp in Python</title><link>https://julien.danjou.info/blog/lisp-python-hy/</link><guid isPermaLink="true">https://julien.danjou.info/blog/lisp-python-hy/</guid><description>I&apos;ve meant to look at Hy since Paul Tagliamonte started to talk to me about it, but never took a chance until now.</description><pubDate>Wed, 03 Apr 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve meant to look at &lt;a href=&quot;http://github.com/paultag/hy&quot;&gt;Hy&lt;/a&gt; since &lt;a href=&quot;http://blog.pault.ag/&quot;&gt;Paul Tagliamonte&lt;/a&gt; started to talk to me about it, but never took a chance until now. Yesterday, Paul indicated it was a good time for me to start looking at it, so I spent a few hours playing.&lt;/p&gt;
&lt;h2&gt;But what&apos;s Hy?&lt;/h2&gt;
&lt;p&gt;Python is very nice: it has a great community and a wide range of useful libraries. But let&apos;s face it, it misses a great language.&lt;/p&gt;
&lt;p&gt;Hy is an implementation of a &lt;a href=&quot;http://en.wikipedia.org/wiki/Lisp_(programming_language)&quot;&gt;Lisp&lt;/a&gt; on top of Python.&lt;/p&gt;
&lt;p&gt;Technically, Hy is built directly with a custom made parser (for now) which then translates expressions using the &lt;a href=&quot;http://docs.python.org/2/library/ast.html&quot;&gt;Python AST&lt;/a&gt; module to generate code, which is then run by Python. Therefore, it shares the same properties as Python, and is a Lisp-1 (i.e. with a single namespace for symbols and functions).&lt;/p&gt;
&lt;p&gt;If you&apos;re interested to listen Paul talking about Hy during last PyCon US, I recommend watching his lightning talk. As the name implies, it&apos;s only a few minutes long.&lt;/p&gt;
&lt;h2&gt;Does it work?&lt;/h2&gt;
&lt;p&gt;I&apos;ve been cloning the code and played around a bit with Hy. And to my greatest surprise and pleasure, it works quite well. You can imagine writing Python from there easily. Part of the syntax smells like &lt;a href=&quot;http://clojure.org&quot;&gt;Clojure&lt;/a&gt;&apos;s, which looks like a good thing since they&apos;re playing in the same area.&lt;/p&gt;
&lt;p&gt;You can try a &lt;a href=&quot;http://hy.pault.ag/&quot;&gt;Hy REPL&lt;/a&gt; in your Web browser if you want.&lt;/p&gt;
&lt;p&gt;Here&apos;s what some code look like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(import requests)

(setv req (requests.get &quot;http://hy.pault.ag&quot;))
(if (= req.status_code 200)
  (for (kv (.iteritems req.headers))
    (print kv))
  (throw (Exception &quot;Wrong status code&quot;)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code would ouput:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(&apos;date&apos;, &apos;Wed, 03 Apr 2013 12:09:23 GMT&apos;)
(&apos;connection&apos;, &apos;keep-alive&apos;)
(&apos;content-encoding&apos;, &apos;gzip&apos;)
(&apos;transfer-encoding&apos;, &apos;chunked&apos;)
(&apos;content-type&apos;, &apos;text/html; charset=utf-8&apos;)
(&apos;server&apos;, &apos;nginx/1.2.6&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, it&apos;s really simple to write Lispy code that really uses Python idioms.&lt;/p&gt;
&lt;p&gt;There&apos;s obviously still a lots of missing features in Hy. The language if far from complete and many parts are moving, but it&apos;s really promising, and Paul&apos;s doing a great job implementing every idea.&lt;/p&gt;
&lt;p&gt;I actually started to hack a bit on Hy, and will try to continue to do so, since I&apos;m really eager to learn a bit more about both Lisp and Python internals in the process. I&apos;ve already send a few patches on small bugs I&apos;ve encountered, and proposed a few ideas. It&apos;s really exciting to be able to influence early a language design that I&apos;ll love to use! Being a recent fan of Common Lisp, I tend to grab the good stuff from it to add them into Hy.&lt;/p&gt;
</content:encoded></item><item><title>Announcing Climate, the OpenStack capacity leasing project</title><link>https://julien.danjou.info/blog/openstack-climate-capacity-leasing/</link><guid isPermaLink="true">https://julien.danjou.info/blog/openstack-climate-capacity-leasing/</guid><description>While working on the XLcloud project (HPC on cloud) it appeared clear to us that OpenStack was missing a critical component towards resource reservations.</description><pubDate>Mon, 25 Mar 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;While working on the &lt;a href=&quot;http://xlcloud.org/bin/view/Main/&quot;&gt;XLcloud project&lt;/a&gt; (HPC on cloud) it appeared clear to us that OpenStack was missing a critical component towards resource reservations.&lt;/p&gt;
&lt;p&gt;A capacity leasing service is something really needed by service providers, especially in the context of cloud platforms dedicated to HPC style workload. Instead of building something really specific, the decision has been made to build a new standalone OpenStack components aiming to provide this kind of functionnality to OpenStack. In the spirit of others OpenStack components, it will be extensible to fullfil a large panel of needs around this problematic.&lt;/p&gt;
&lt;p&gt;The project is named &lt;a href=&quot;http://launchpad.net/climate&quot;&gt;Climate&lt;/a&gt;, and is hosted on &lt;a href=&quot;http://ci.openstack.org/stackforge.html&quot;&gt;StackForge&lt;/a&gt;. It will follow the standard OpenStack development modal. This service will be able to handle a calendar of reservations for various resources, based on various criteria.&lt;/p&gt;
&lt;p&gt;The project is still at its early design stage, and we plan to have a unconference session during &lt;a href=&quot;http://www.openstack.org/summit/portland-2013/&quot;&gt;the next OpenStack summit in Portland&lt;/a&gt; to present our plans and ideas for the future!&lt;/p&gt;
</content:encoded></item><item><title>Ceilometer bug squash day #2</title><link>https://julien.danjou.info/blog/ceilometer-bug-squash-day-2/</link><guid isPermaLink="true">https://julien.danjou.info/blog/ceilometer-bug-squash-day-2/</guid><description>The Ceilometer team is pleased to announce that tomorrow Tuesday 5th March 2013 will be the second bug squash day for Ceilometer.</description><pubDate>Mon, 04 Mar 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The Ceilometer team is pleased &lt;a href=&quot;http://lists.openstack.org/pipermail/openstack-dev/2013-March/006188.html&quot;&gt;to announce&lt;/a&gt; that tomorrow &lt;a href=&quot;http://wiki.openstack.org/Ceilometer/BugSquashingDay/20130304&quot;&gt;Tuesday 5th March 2013 will be the second bug squash day for Ceilometer&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We wrote an extensive page about &lt;a href=&quot;http://wiki.openstack.org/Ceilometer/Contributing&quot;&gt;how you can contribute to Ceilometer&lt;/a&gt;, from updating the documentation, to fixing bugs. There&apos;s a lot you can do. We&apos;ve good support for Ceilometer built into &lt;a href=&quot;http://devstack.org&quot;&gt;Devstack&lt;/a&gt;, so installing a development platform is really easy.&lt;/p&gt;
&lt;p&gt;The main goal for this bug day will be to put Ceilometer in the best possible shape before the &lt;em&gt;grizzly-rc1&lt;/em&gt; release arrives (14th March 2013). This version of Ceilometer &lt;em&gt;should&lt;/em&gt; be the last one before the final Grizzly release, so it&apos;s a pretty important one.&lt;/p&gt;
&lt;p&gt;We&apos;ll be hanging out on the &lt;em&gt;#openstack-metering&lt;/em&gt; IRC channel on &lt;a href=&quot;http://freenode.net&quot;&gt;Freenode&lt;/a&gt;, as usual, so feel free to come by and join us!&lt;/p&gt;
</content:encoded></item><item><title>OpenStack Ceilometer and Heat projects graduated</title><link>https://julien.danjou.info/blog/openstack-ceilometer-heat-graduated/</link><guid isPermaLink="true">https://julien.danjou.info/blog/openstack-ceilometer-heat-graduated/</guid><description>The OpenStack Technical Committee has voted these last weeks about graduation of Heat and Ceilometer, to change their status from incubation to integrated.</description><pubDate>Wed, 27 Feb 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/openstack-tech-committee.jpg&quot; alt=&quot;openstack-tech-committee&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;http://www.openstack.org/foundation/technical-committee/&quot;&gt;OpenStack Technical Committee&lt;/a&gt; has voted these last weeks about graduation of &lt;a href=&quot;https://launchpad.net/heat&quot;&gt;Heat&lt;/a&gt; and &lt;a href=&quot;http://launchpad.net/ceilometer&quot;&gt;Ceilometer&lt;/a&gt;, to change their status from &lt;strong&gt;incubation&lt;/strong&gt; to &lt;strong&gt;integrated&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The details of the discussion can be found in the &lt;a href=&quot;http://eavesdrop.openstack.org/meetings/tc/2013/&quot;&gt;TC IRC meetings logs&lt;/a&gt; for the brave. The results are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Approve graduation of Heat (to be integrated in common Havana release)? yes: 10, abstain: 1, no: 1&lt;/li&gt;
&lt;li&gt;Approve graduation of Ceilometer (to be integrated in common Havana release)? yes: 11, abstain: 1&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Therefore both projects have been graduated from &lt;em&gt;Incubation&lt;/em&gt; to &lt;em&gt;Integrated&lt;/em&gt; status. That means that Heat and Ceilometer will be released as part as OpenStack for the next release cycle &lt;em&gt;Havana&lt;/em&gt;, due in Autumn 2013.&lt;/p&gt;
&lt;p&gt;For people being curious, we the Ceilometer team put up a &lt;a href=&quot;https://wiki.openstack.org/wiki/Ceilometer/Graduation&quot;&gt;nice wiki page about our status and what we think we were ready to jump&lt;/a&gt;. For the curious, The &lt;a href=&quot;https://wiki.openstack.org/wiki/Governance/Foundation/TechnicalCommittee&quot;&gt;OpenStack Technical Committee charter&lt;/a&gt; has some explanations about the incubation and integration process.&lt;/p&gt;
&lt;h2&gt;What about Grizzly?&lt;/h2&gt;
&lt;p&gt;Both projects will be released with Grizzly too, obviously, since they already follow the release process of OpenStack.&lt;/p&gt;
&lt;h2&gt;What about core?&lt;/h2&gt;
&lt;p&gt;The question that has been raised several times to me is if that means the projects are becoming &lt;em&gt;Core&lt;/em&gt; projects. The answer is no, because how to become a &lt;em&gt;Core&lt;/em&gt; project is still under discussion and is more a matter for the &lt;em&gt;Board of Directors&lt;/em&gt; than the &lt;em&gt;Technical Committee&lt;/em&gt;. But this is definitely a step in this direction.&lt;/p&gt;
&lt;p&gt;Anyway, from a technical point of view, this means both projects are now onboard with other OpenStack components so you can enjoy them!&lt;/p&gt;
</content:encoded></item><item><title>Cloud tools for Debian</title><link>https://julien.danjou.info/blog/cloud-init-utils-debian/</link><guid isPermaLink="true">https://julien.danjou.info/blog/cloud-init-utils-debian/</guid><description>Recently, I&apos;ve worked on the cloud utilities that are provided as standard in Ubuntu, and I ported them to Debian. Let&apos;s see how that brings Debian to the cloud!  Basics of a cloud image When starting</description><pubDate>Wed, 13 Feb 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recently, I&apos;ve worked on the cloud utilities that are provided as standard in Ubuntu, and I ported them to Debian. Let&apos;s see how that brings Debian to the cloud!&lt;/p&gt;
&lt;h2&gt;Basics of a cloud image&lt;/h2&gt;
&lt;p&gt;When starting an instance on a IaaS platform, your instance image is raw, un-configured. Therefore, you need to have a way to configure it automagically at boot time, based on what you want to do with it. Usually, IaaS platforms provides for this a metadata server, like &lt;a href=&quot;http://aws.amazon.com/ec2&quot;&gt;Amazon EC2&lt;/a&gt; does. It&apos;s a special HTTP server listening on a special and hard-coded IP address that your instance can request to know basic information about itself, like its hostname, and retrieve basic user metadata to auto-configure itself. You can check the &lt;a href=&quot;http://docs.openstack.org/trunk/openstack-compute/admin/content/metadata-service.html&quot;&gt;documentation about the OpenStack metadata service&lt;/a&gt; for more information.&lt;/p&gt;
&lt;p&gt;Also, image have a predefined size at upload time. So when you run it on a platform, the disk size you request is usually bigger than the size of your image disk: you mayneed to resize and grow your image to use the full disk space that is allocated to your instance.&lt;/p&gt;
&lt;h2&gt;Needed tools&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/debian-cloud-1.jpg&quot; alt=&quot;debian-cloud-1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;To run a cloud platform, and especially &lt;a href=&quot;http://aws.amazon.com/ec2&quot;&gt;Amazon EC2&lt;/a&gt; or &lt;a href=&quot;http://openstack.org&quot;&gt;OpenStack&lt;/a&gt;, you need to configure and update your image based on the context you&apos;re started in. This also includes extending your template image disk to use the full available disk size provided to the running instance.&lt;/p&gt;
&lt;p&gt;Ubuntu provides a set of cloud utils, which is actually composed of different source packages (&lt;em&gt;cloud-init&lt;/em&gt;, &lt;em&gt;cloud-utils&lt;/em&gt; and &lt;em&gt;clout-initiramfs-tools&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;Combined, these 3 packages will allow you to run a number of step, from disk resize at boot time to Puppet configuration handling.&lt;/p&gt;
&lt;p&gt;So &lt;em&gt;Ubuntu&lt;/em&gt; got this working right a long time ago, but unfortunately, Debian was really late on that.&lt;/p&gt;
&lt;p&gt;Until now.&lt;/p&gt;
&lt;p&gt;I&apos;ve worked on getting these into Debian, and you can now find these 3 packages adapted and uploaded to Debian sid.&lt;/p&gt;
&lt;p&gt;All you need to do, is to build a Debian image and then run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apt-get install cloud-init cloud-tools cloud-initiramfs-growroot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And voilà: at the next reboot, your instance will extend its root partition size to the full available disk size, and ask the metadata server to configure things like its hostname.&lt;/p&gt;
&lt;p&gt;The packages sources are available on Debian&apos;s git server for &lt;a href=&quot;http://anonscm.debian.org/gitweb/?p=collab-maint/cloud-utils.git;a=summary&quot;&gt;cloud-utils&lt;/a&gt;&lt;br /&gt;
and &lt;a href=&quot;http://anonscm.debian.org/gitweb/?p=collab-maint/cloud-initramfs-tools.git;a=summary&quot;&gt;cloud-initramfs-tools&lt;/a&gt; and you can build them yourself until the packages are processed by ftp-master and get out of the &lt;a href=&quot;http://ftp-master.debian.org/new.html&quot;&gt;NEW queue&lt;/a&gt;. cloud-init on the other hand is directly &lt;a href=&quot;http://packages.debian.org/search?keywords=cloud-init&quot;&gt;available in sid&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One of next steps would probably be to build or enhance a tool like &lt;a href=&quot;https://launchpad.net/vmbuilder&quot;&gt;vmbuilder&lt;/a&gt; to be able to build cloud-compatible Debian images with a simple command line.&lt;/p&gt;
</content:encoded></item><item><title>Extending Swift with middleware: example with ClamAV</title><link>https://julien.danjou.info/blog/extending-swift-with-a-middleware-clamav/</link><guid isPermaLink="true">https://julien.danjou.info/blog/extending-swift-with-a-middleware-clamav/</guid><description>In this article, I&apos;m going to explain you how you can extend Swift, the OpenStack Object Storage project, so it performs extra action on files at upload or at download time.</description><pubDate>Tue, 22 Jan 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In this article, I&apos;m going to explain you how you can extend &lt;a href=&quot;http://launchpad.net/swift&quot;&gt;Swift&lt;/a&gt;, the &lt;a href=&quot;http://openstack.org&quot;&gt;OpenStack&lt;/a&gt; Object Storage project, so it performs extra action on files at upload or at download time.&lt;/p&gt;
&lt;p&gt;We&apos;re going to build an anti-virus filter inside Swift. The goal is to refuse uploaded data if they contain a virus. To help us with virus analyses, we&apos;ll use &lt;a href=&quot;http://www.clamav.net&quot;&gt;ClamAV&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;WSGI, paste and middleware&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/lolcat-tube.jpg&quot; alt=&quot;lolcat-tube&quot; /&gt;&lt;/p&gt;
&lt;p&gt;To do our content analysis, the best place to hook in the Swift architecture is at the beginning of every request, on &lt;strong&gt;swift-proxy&lt;/strong&gt;, before the file is actually stored on the cluster. Swift proxy uses, like many other OpenStack projects, &lt;a href=&quot;https://pypi.python.org/pypi/Paste&quot;&gt;paste&lt;/a&gt; to build his HTTP architecture.&lt;/p&gt;
&lt;p&gt;Paste uses WSGI and provides an architecture based on a pipeline. The pipeline is composed of a succession of middleware, ending with one application. Each middleware has the chance to look at the request or at the response, can modify it, and then pass it to the following middleware. The latest component of the pipeline is the real application, and in this case, the Swift proxy server.&lt;/p&gt;
&lt;p&gt;If you&apos;ve already deployed Swift, you encountered a default pipeline in the &lt;em&gt;swift-proxy.conf&lt;/em&gt; configuration file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[pipeline:main]
pipeline = catch_errors healthcheck cache ratelimit tempauth proxy-logging proxy-server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a really basic pipeline with a few middleware. The first one catches error, the second one is in charge to return &lt;em&gt;200 OK&lt;/em&gt; response if you send a &lt;code&gt;GET /healthcheck&lt;/code&gt; request on your proxy server. The third one is in charge of caching, the fourth one is used for rate limiting, the fifth for authentication, the sixth one for logging, and the final one is the actual proxy server, in charge of proxying the request to the account, container, or object servers (the others components of Swift). Of course, we could remove or add any of the middleware here at our convenience.&lt;/p&gt;
&lt;p&gt;Be aware that the order matters: for example, if you put &lt;em&gt;healthcheck&lt;/em&gt; after &lt;em&gt;tempauth&lt;/em&gt;, you won&apos;t be able to access the &lt;em&gt;/healthcheck&lt;/em&gt; URL without being authenticated!&lt;/p&gt;
&lt;h2&gt;ClamAV&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/clamav.png&quot; alt=&quot;clamav&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If you don&apos;t know &lt;a href=&quot;http://clamav.org&quot;&gt;ClamAV&lt;/a&gt;, it&apos;s an antivirus engine designed for detecting trojans, viruses, malware and other malicious threats. Wwe&apos;re going to use it to scan every incoming file. To build the middleware, we&apos;ll use the Python binding &lt;a href=&quot;http://pypi.python.org/pypi/clamd&quot;&gt;pyclamd&lt;/a&gt;. The API is quite simple, see:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; import pyclamd
&amp;gt;&amp;gt;&amp;gt; pyclamd.init_unix_socket(&apos;/var/run/clamav/clamd.ctl&apos;)
&amp;gt;&amp;gt;&amp;gt; print pyclamd.scan_stream(pyclamd.EICAR)
{&apos;stream&apos;: &apos;Eicar-Test-Signature(44d88612fea8a8f36de82e1278abb02f:68)&apos;}
&amp;gt;&amp;gt;&amp;gt; print pyclamd.scan_stream(&quot;safe!&quot;)
None
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Anatomy of a WSGI middleware&lt;/h2&gt;
&lt;p&gt;Your WSGI middleware should consist of a callable object. Usually this is done with a class implementing the &lt;em&gt;__call__&lt;/em&gt; method. Here&apos;s a basic boilerplate:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class SwiftClamavMiddleware(object):
    &quot;&quot;&quot;Middleware doing virus scan for Swift.&quot;&quot;&quot;

    def __init__(self, app, conf):
        # app is the final application
        self.app = app

    def __call__(self, env, start_response):
        return self.app(env, start_response)

def filter_factory(global_conf, **local_conf):
    conf = global_conf.copy()
    conf.update(local_conf)

    def clamav_filter(app):
        return SwiftClamavMiddleware(app, conf)
    return clamav_filter
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&apos;m not going to expand more on why this is built this way, but if you want to have more info on this kind of filter middleware, you can read &lt;a href=&quot;http://pythonpaste.org/deploy/#paste-filter-factory&quot;&gt;their documentation on Paste&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This middleware will just do nothing as it is. It&apos;s going to simply pass all requests it receives to the final application, and returns the result.&lt;/p&gt;
&lt;h2&gt;Testing our basic middleware&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/lolcat-testing.jpg&quot; alt=&quot;lolcat-testing&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now is a really good time to add unit tests. I hope you didn&apos;t think we were going to write code without some tests, right? It&apos;s really easy to test a middleware, as we&apos;re going to use &lt;a href=&quot;http://webob.org/&quot;&gt;WebOb&lt;/a&gt; for that.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import unittest
from webob import Request

class FakeApp(object):
    def __call__(self, env, start_response):
        return Response(body=&quot;FAKE APP&quot;)(env, start_response)

class TestSwiftClamavMiddleware(unittest.TestCase):

    def setUp(self):
        self.app = SwiftClamavMiddleware(FakeApp(), {})

    def test_simple_request(self):
        resp = Request.blank(&apos;/&apos;,
                             environ={
                                 &apos;REQUEST_METHOD&apos;: &apos;GET&apos;,
                             }).get_response(self.app)
        self.assertEqual(resp.body, &quot;FAKE APP&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We create a FakeApp class, that represents a fake WSGI application. You could also use a real application, or write a fake application looking like the one you want to test. It&apos;ll require more time, but your tests will be closer to the reality.&lt;/p&gt;
&lt;p&gt;Here we write the simplest test we can for our middleware. We&apos;re just sending a &lt;em&gt;GET /&lt;/em&gt; request to it, so it passes the request to the final application and returns the result. It is transparent, it does nothing.&lt;/p&gt;
&lt;p&gt;Now, with that solid base we&apos;ll able to add more features and test these features incrementally.&lt;/p&gt;
&lt;h2&gt;Plugging ClamAV in&lt;/h2&gt;
&lt;p&gt;With our base ready, we can start thinking about how to plug ClamAV in. What we want to check here, is the content of the file when it&apos;s uploaded. If we refer to the &lt;a href=&quot;http://docs.openstack.org/api/openstack-object-storage/1.0/content/&quot;&gt;OpenStack object storage API&lt;/a&gt;, a file upload is done via a &lt;em&gt;PUT&lt;/em&gt; request, so we&apos;re going to limit the check to that kind of requests. Obviously, more checks could be added, but we&apos;ll keep things simple here for the sake of comprehensibility.&lt;/p&gt;
&lt;p&gt;With WSGI, the content of the request is available in &lt;code&gt;env[&apos;wsgi.input&apos;]&lt;/code&gt; as an object implementing a file interface. We&apos;ll scan that stream with ClamAV to check for viruses.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import pyclamd
from webob import Response

class SwiftClamavMiddleware(object):
    &quot;&quot;&quot;Middleware doing virus scan for Swift.&quot;&quot;&quot;

    def __init__(self, app, conf):
        pyclamd.init_unix_socket(&apos;/var/run/clamav/clamd.ctl&apos;)
        # app is the final application
        self.app = app

    def __call__(self, env, start_response):
        if env[&apos;REQUEST_METHOD&apos;] == &quot;PUT&quot;:
            # We have to read the whole content in memory because pyclamd
            # forces us to, but this is a bad idea if the file is huge.
            scan = pyclamd.scan_stream(env[&apos;wsgi.input&apos;].read())
            if scan:
                return Response(status=403,
                                body=&quot;Virus %s detected&quot; % scan[&apos;stream&apos;],
                                content_type=&quot;text/plain&quot;)(env, start_response)
        return self.app(env, start_response)

def filter_factory(global_conf, **local_conf):
    conf = global_conf.copy()
    conf.update(local_conf)

    def clamav_filter(app):
        return SwiftClamavMiddleware(app, conf)
    return clamav_filter
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&apos;s it. We only check for &lt;em&gt;PUT&lt;/em&gt; requests and if there&apos;s a virus in the file, we return a 403 Forbidden error with the name of the detected virus, bypassing entirely the rest of the middleware chain and the application handling.&lt;/p&gt;
&lt;p&gt;Then, we can simply test it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import unittest
from cStringIO import StringIO
from webob import Request, Response

class FakeApp(object):
    def __call__(self, env, start_response):
        return Response(body=&quot;FAKE APP&quot;)(env, start_response)

class TestSwiftClamavMiddleware(unittest.TestCase):
    def setUp(self):
        self.app = SwiftClamavMiddleware(FakeApp(), {})

    def test_put_empty(self):
        resp = Request.blank(&apos;/v1/account/container/object&apos;,
                             environ={
                                 &apos;REQUEST_METHOD&apos;: &apos;PUT&apos;,
                             }).get_response(self.app)
        self.assertEqual(resp.body, &quot;FAKE APP&quot;)

    def test_put_no_virus(self):
        resp = Request.blank(&apos;/v1/account/container/object&apos;,
                             environ={
                                 &apos;REQUEST_METHOD&apos;: &apos;PUT&apos;,
                                 &apos;wsgi.input&apos;: StringIO(&apos;foobar&apos;)
                             }).get_response(self.app)
        self.assertEqual(resp.body, &quot;FAKE APP&quot;)

    def test_put_virus(self):
        resp = Request.blank(&apos;/v1/account/container/object&apos;,
                             environ={
                                 &apos;REQUEST_METHOD&apos;: &apos;PUT&apos;,
                                 &apos;wsgi.input&apos;: StringIO(pyclamd.EICAR)
                             }).get_response(self.app)
        self.assertEqual(resp.status_code, 403)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first test &lt;em&gt;test_put_empty&lt;/em&gt; simulates an empty &lt;em&gt;PUT&lt;/em&gt; request. The second one, &lt;em&gt;test_put_no_virus&lt;/em&gt; simulates a regular &lt;em&gt;PUT&lt;/em&gt; request but with a simple file containing no virus.&lt;/p&gt;
&lt;p&gt;Finally, the third and last test simulates the upload of a virus using the &lt;a href=&quot;http://www.eicar.org/&quot;&gt;EICAR&lt;/a&gt; test file. This is a special test file that is recognized as a virus, even if it&apos;s not real one. Very handy for testing virus detection software!&lt;/p&gt;
&lt;h2&gt;Configuring Swift proxy&lt;/h2&gt;
&lt;p&gt;Our middleware is ready! We can configure Swift&apos;s proxy server to use it. We need to add the following lines to our &lt;em&gt;swift-proxy.conf&lt;/em&gt; to teach it how to load the filter:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[filter:clamav]
paste.filter_factory = swiftclamav:filter_factory
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&apos;ll assume that our Python modules is named &lt;em&gt;swiftclamava&lt;/em&gt; here. Now that we&apos;ve defined our filter and how to load it, we can use it in our pipeline:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[pipeline:main]
pipeline = catch_errors healthcheck cache ratelimit tempauth clamav proxy-logging proxy-server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just before reaching the &lt;em&gt;proxy-server&lt;/em&gt;, and after the user being authenticated, the content will be scanned for viruses. It&apos;s important here to put this after authentication for example, because otherwise we may scan content that will get rejected by the authtemp module, thus scanning for nothing!&lt;/p&gt;
&lt;h2&gt;Beyond scanning&lt;/h2&gt;
&lt;p&gt;And voilà, we now have a simple middleware testing uploaded content and refusing infected files. We could enhance it with various other things, like configuration handling, but I&apos;ll let that as an exercise for the interested readers.&lt;/p&gt;
&lt;p&gt;We didn&apos;t exploited it here, but note that you can also manipulate request headers and modify them if needed. For example, we could have added a header &lt;em&gt;X-Object-Meta-Scanned-By: ClamAV&lt;/em&gt; to indicates that the file has been scanned by ClamAV.&lt;/p&gt;
&lt;p&gt;You should now be able to build your own middleware doing whatever you want with uploaded data. Happy hacking!&lt;/p&gt;
</content:encoded></item><item><title>Overriding cl-json object encoding</title><link>https://julien.danjou.info/blog/cl-postmodern-dao-json/</link><guid isPermaLink="true">https://julien.danjou.info/blog/cl-postmodern-dao-json/</guid><description>CL-JSON provides an encoder for Lisp data structures and objects to JSON format. Unfortunately, in some case, its default encoding mechanism for CLOS objects isn&apos;t exactly doing the right thing.</description><pubDate>Fri, 11 Jan 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;http://common-lisp.net/project/cl-json/&quot;&gt;CL-JSON&lt;/a&gt; provides an encoder for Lisp data structures and objects to JSON format. Unfortunately, in some case, its default encoding mechanism for CLOS objects isn&apos;t exactly doing the right thing. I&apos;ll show you how Common Lisp makes it easy to change that.&lt;/p&gt;
&lt;h2&gt;Identifying the problem&lt;/h2&gt;
&lt;h3&gt;CL-JSON &amp;amp; CLOS&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;CL-JSON&lt;/em&gt; mechanism encoding CLOS object is really neat. Let&apos;s see how it works for a simple case:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(defclass kitten ()
  ((tail :initarg :tail)))

(json:encode-json-to-string (make-instance &apos;kitten :tail &apos;black))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;will produce:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{&quot;tail&quot;:&quot;black&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Still using CL-JSON, we can also decode the JSON object to a CLOS object:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(slot-value
 (json:with-decoder-simple-clos-semantics
   (json:decode-json-from-string &quot;{\&quot;tail\&quot;:\&quot;black\&quot;}&quot;))
 :tail)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That code will return &lt;em&gt;&quot;black&quot;&lt;/em&gt;. Note that it&apos;s also possible to specify which class should be used when decoding objects, but that&apos;s beyond the purpose of this article.&lt;/p&gt;
&lt;h3&gt;Postmodern&lt;/h3&gt;
&lt;p&gt;Now, let&apos;s introduce &lt;a href=&quot;http://marijnhaverbeke.nl/postmodern/&quot;&gt;Postmodern&lt;/a&gt;, a wonderful Common Lisp system providing access to the wonderful &lt;a href=&quot;http://postgresql.org&quot;&gt;PostgreSQL&lt;/a&gt; database. It also provides a simple system to map rows in a database to CLOS classes, called DAO for &lt;em&gt;Database access objects&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;With this, we can easily store our &lt;em&gt;kitten&lt;/em&gt; into a table.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(defclass kitten ()
  ((tail :initarg :tail))
  (:metaclass postmodern:dao-class))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we try to encode this to JSON, it will produce the exact same result seen previously.&lt;/p&gt;
&lt;p&gt;The problem is what happens when one of our column has a &lt;em&gt;NULL&lt;/em&gt; value. Postmodern encodes this using the &lt;em&gt;:null&lt;/em&gt; symbol.&lt;/p&gt;
&lt;p&gt;So this code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(defclass kitten ()
  ((tail :initarg :tail :col-type (or s-sql:db-null text)))
  (:metaclass postmodern:dao-class))

(postmodern:deftable kitten
  (postmodern:!dao-def))

(postmodern:connect-toplevel …)

(postmodern:create-table &apos;kitten)

(json:encode-json-to-string
  (postmodern:make-dao &apos;kitten))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;will return:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;{&quot;tail&quot;:&quot;null&quot;}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fail! The fact that the column is &lt;em&gt;NULL&lt;/em&gt; is represented by the &lt;em&gt;:null&lt;/em&gt; symbol. And CL-JSON encodes all symbols as string.&lt;/p&gt;
&lt;p&gt;This is not at all what we want here!&lt;/p&gt;
&lt;h2&gt;Overriding encode-json&lt;/h2&gt;
&lt;p&gt;CL-JSON provides and uses the &lt;em&gt;encode-json&lt;/em&gt; method to encode all kind of object. It is defined as a &lt;em&gt;generic function&lt;/em&gt;, and a lot of different methods are implemented to handle the different standard Common Lisp types. The one used for &lt;em&gt;standard-object&lt;/em&gt; is defined liked that:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(defmethod encode-json ((o standard-object)
                        &amp;amp;optional (stream *json-output*))
  &quot;Write the JSON representation (Object) of the CLOS object O to
STREAM (or to *JSON-OUTPUT*).&quot;
  (with-object (stream)
    (map-slots (stream-object-member-encoder stream) o)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All we need to do here, is to create a new method for our &lt;em&gt;kitten&lt;/em&gt; objects, that handles correctly the &lt;em&gt;:null&lt;/em&gt; case.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(defclass kitten ()
  ((tail :initarg :tail :col-type (or s-sql:db-null text)))
  (:metaclass postmodern:dao-class))

(export &apos;kitten)

;; Switch package just to define the new method
(in-package :json)
(defmethod encode-json ((o cl-user:kitten)
                        &amp;amp;optional (stream json:*json-output*))
  &quot;Write the JSON representation (Object) of the postmodern DAO CLOS object
O to STREAM (or to *JSON-OUTPUT*).&quot;
  (with-object (stream)
    (map-slots (lambda (key value)
                 (as-object-member (key stream)
                   (encode-json (if (eq value :null) nil value) stream)))
               o)))

;; Go back into our package
(in-package :cl-user)

(postmodern:deftable kitten
  (postmodern:!dao-def))

(postmodern:connect-toplevel …)

(postmodern:create-table &apos;kitten)

(json:encode-json-to-string
  (postmodern:make-dao &apos;kitten))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that new method, as soon as we encounter a &lt;em&gt;:null&lt;/em&gt; symbol as a value for an object&apos;s slot, we replace it by &lt;em&gt;nil&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Now if we try to encode another &lt;em&gt;kitten&lt;/em&gt;, we&apos;ll get:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{&quot;tail&quot;:null}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;which is far better for our JavaScript data consumers!&lt;/p&gt;
&lt;p&gt;In the end, I think that this kind of trick is feasible that easily because of the way CLOS provides its generic method implementation.&lt;br /&gt;
The fact that methods don&apos;t belong to any class makes the extension of every program, library and class so much easier. Doing this in another language like Java would likely by impossible, and in Python it would unlikely be as clean as it is done in Common Lisp.&lt;/p&gt;
&lt;p&gt;The ability to teach &lt;em&gt;any&lt;/em&gt; library about how it should handle your class just by defining a new method is really handy!&lt;/p&gt;
</content:encoded></item><item><title>Integrating cl-irc and cl-async</title><link>https://julien.danjou.info/blog/cl-irc-async/</link><guid isPermaLink="true">https://julien.danjou.info/blog/cl-irc-async/</guid><description>Recently, I&apos;ve started programming in Common Lisp.</description><pubDate>Fri, 04 Jan 2013 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recently, I&apos;ve started programming in &lt;a href=&quot;http://common-lisp.net&quot;&gt;Common Lisp&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;My idea here is to use &lt;a href=&quot;http://www.cliki.net/cl-irc&quot;&gt;cl-irc&lt;/a&gt;, an IRC library into an &lt;a href=&quot;http://en.wikipedia.org/wiki/Event_loop&quot;&gt;event loop&lt;/a&gt;. This can be really useful, for example to trigger action based on time, using timers.&lt;/p&gt;
&lt;h2&gt;Creating a connection&lt;/h2&gt;
&lt;p&gt;The first step is to create a basic &lt;em&gt;cl-irc:connection&lt;/em&gt; object on our own. This can be achieved easily with this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(require :cl-irc)

(defun connect (server)
  (cl-irc:make-connection :connection-type &apos;cl-irc:connection
                                              :client-stream t
                                              :network-stream ?
                                              :server-name server))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will return a &lt;em&gt;cl-irc:connection&lt;/em&gt; object, logging to stdout (&lt;em&gt;:client-stream t&lt;/em&gt;) and having the server name &lt;em&gt;server&lt;/em&gt;. Note that the server name could be any string.&lt;/p&gt;
&lt;p&gt;You probably noticed the &lt;em&gt;?&lt;/em&gt; I used as :network-stream value. This is not a real and working value: this should be a stream established to the IRC server you want to chat with. This is where we&apos;ll need to use &lt;a href=&quot;http://orthecreedence.github.com/cl-async/tcp#tcp-connect&quot;&gt;&lt;code&gt;cl-async:tcp connect&lt;/code&gt;&lt;/a&gt; to establish a TCP connection.&lt;/p&gt;
&lt;p&gt;As you can read in this function&apos;s documentation, all we need to pass is the server address, two callbacks for read and general events, and the &lt;em&gt;:stream&lt;/em&gt; option to get a stream rather than a socket.&lt;/p&gt;
&lt;p&gt;So you would do something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(require :cl-irc)
(require :cl-async)

(defun connection-socket-read (socket stream)
  (format t &quot;We should read the IRC message from ~a ~%&quot; stream))

(defun connection-socket-event (ev)
  (format t &quot;Socket event: ~a~%&quot; ev))

(defun connect (server &amp;amp;optional (port 6667))
  (cl-irc:make-connection :connection-type &apos;cl-irc:connection
                          :client-stream t
                          :network-stream (as:tcp-connect server port
                                                          #&apos;connection-socket-read
                                                          #&apos;connection-socket-event
                                                          :stream t)
                          :server-name server))

(as:start-event-loop (lambda () (connect &quot;irc.oftc.net&quot;)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you run this program, it will connect to the OFTC IRC server, and then notice you each time the server is sending you a message.&lt;/p&gt;
&lt;p&gt;Therefore our problem here is how we you treat the message read from the stream in &lt;code&gt;connection-socket-read&lt;/code&gt; and handle them in the name of our connection object you used? We can&apos;t link both together at this point.&lt;/p&gt;
&lt;p&gt;We can&apos;t build a closure, because as the time we use &lt;em&gt;as:tcp-connect&lt;/em&gt; we don&apos;t have the &lt;em&gt;cl-irc:connection&lt;/em&gt; instance. Also we can&apos;t change easily the &lt;em&gt;read-cb&lt;/em&gt; parameter of our &lt;em&gt;network-stream&lt;/em&gt; established by &lt;em&gt;as:tcp-connect&lt;/em&gt;, simply because &lt;em&gt;cl-async&lt;/em&gt; doesn&apos;t use to do allow that.&lt;/p&gt;
&lt;h2&gt;Building a closure&lt;/h2&gt;
&lt;p&gt;So one solution here is to hack &lt;em&gt;cl-irc:make-connection&lt;/em&gt; so we can build an &lt;em&gt;cl-irc:connection&lt;/em&gt; instance without providing in advance the &lt;em&gt;network-stream&lt;/em&gt;, allowing us to build a closure including the &lt;em&gt;cl-irc:connection&lt;/em&gt; to read event for. This is what we&apos;re going to do in the &lt;code&gt;connect&lt;/code&gt; function.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(require :cl-irc)
(require :cl-async)
(require :flexi-streams)

(defun connection-socket-read (connection)
  (loop for message = (cl-irc::read-irc-message connection)
        while message
        do (cl-irc:irc-message-event connection message)))

(defun connection-socket-event (ev)
  (format t &quot;Socket event: ~a~%&quot; ev))

(defun connect (server port nickname
                &amp;amp;key
                  (username nil)
                  (realname nil)
                  (password nil))
  ;; Build an instance of cl-irc:connection, without any network/output stream
  (let* ((connection (make-instance &apos;cl-irc:connection
                                    :user username
                                    :password password
                                    :server-name server
                                    :server-port port
                                    :client-stream t))
         ;; Use as:tcp-connect to build our network stream, and build a
         ;; closure calling `connection-socket-read&apos; with our `connection&apos;
         ;; as arguments
         (network-stream (as:tcp-connect server port
                                         (lambda (socket stream)
                                           (declare (ignore socket stream))
                                           (connection-socket-read connection))
                                         #&apos;connection-socket-event
                                         :stream t)))
    ;; Set the network stream on the connection
    (setf (cl-irc:network-stream connection) network-stream)
    ;; Set the output stream on the connection
    (setf (cl-irc:output-stream connection)
         ;; This is grabbed from cl-irc:make-connection
          (flexi-streams:make-flexi-stream
           network-stream
           :element-type &apos;character
           :external-format &apos;(:utf8 :eol-style :crlf)))

    ;; Now handle the IRC protocol authentication pass
    (unless (null password)
      (cl-irc:pass connection password))
    (cl-irc:nick connection nickname)
    (cl-irc:user- connection (or username nickname) 0 (or realname nickname))
    connection))

(as:start-event-loop (lambda () (connect &quot;irc.oftc.net&quot; 6667 &quot;jd-blog&quot;)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here we are! If we run this, we&apos;re now using an event loop to run &lt;code&gt;cl-irc&lt;/code&gt;. Each time the socket has something to read, the function &lt;code&gt;connection-socket-read&lt;/code&gt; will be called on the non-blocking mode socket. If there&apos;s no message to be read, then the function will exit and the loop will continue to run.&lt;/p&gt;
&lt;h2&gt;Using timers&lt;/h2&gt;
&lt;p&gt;You can now modify the last line with this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(defun say-hello (connection)
  (cl-irc:privmsg connection &quot;#jd-blog&quot; &quot;Hey I read your blog!&quot;)
  (as:delay (lambda () (say-hello connection)) :time 60))

(as:start-event-loop (lambda ()
                       (let ((connection (connect &quot;irc.oftc.net&quot; 6667 &quot;jd-blog&quot;)))
                         (cl-irc:join connection &quot;#jd-blog&quot;)
                         (say-hello connection))))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will connect to the IRC server, join a channel and then say the same sentence every minute.&lt;/p&gt;
&lt;p&gt;Challenge accomplished!&lt;/p&gt;
&lt;p&gt;And I&apos;d like to thank &lt;a href=&quot;http://blog.killtheradio.net/&quot;&gt;Andrew Lyon&lt;/a&gt;, the author of &lt;a href=&quot;https://github.com/orthecreedence/cl-async&quot;&gt;cl-async&lt;/a&gt;, who has been incredibly helpful with my recent experimentations in this area.&lt;/p&gt;
</content:encoded></item><item><title>Ceilometer bug squash day #1</title><link>https://julien.danjou.info/blog/ceilometer-bug-squash-day-1/</link><guid isPermaLink="true">https://julien.danjou.info/blog/ceilometer-bug-squash-day-1/</guid><description>In order to start the year in a good mood, what&apos;s the best than squashing some bugs on OpenStack?</description><pubDate>Mon, 24 Dec 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In order to start the year in a good mood, what&apos;s the best than squashing some bugs on OpenStack?&lt;/p&gt;
&lt;p&gt;Therefore, the Ceilometer team is pleased &lt;a href=&quot;http://lists.openstack.org/pipermail/openstack-dev/2012-December/004161.html&quot;&gt;to announce&lt;/a&gt; that it organizes a &lt;a href=&quot;http://wiki.openstack.org/Ceilometer/BugSquashingDay/20130104&quot;&gt;bug squashing day on the Friday 4th January 2013&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We wrote an extensive page about &lt;a href=&quot;http://wiki.openstack.org/Ceilometer/Contributing&quot;&gt;how you can contribute to Ceilometer&lt;/a&gt;, from updating the documentation, to fixing bugs. There&apos;s a lot you can do. We&apos;ve good support for Ceilometer built into &lt;a href=&quot;http://devstack.org&quot;&gt;Devstack&lt;/a&gt;, so installing a development platform is really easy.&lt;/p&gt;
&lt;p&gt;The main goal on this bug day will be put Ceilometer in the best possible shape before the &lt;em&gt;grizzly-2&lt;/em&gt; milestone arrives (10th January 2013). This version of Ceilometer will aim to keep compatibility with &lt;em&gt;Folsom&lt;/em&gt;, so early deployers can enjoy some of our new features before upgrading to &lt;em&gt;Grizzly&lt;/em&gt;. After that date, we&apos;ll start merging more extensive changes.&lt;/p&gt;
&lt;p&gt;We&apos;ll be hanging out on the &lt;em&gt;#openstack-metering&lt;/em&gt; IRC channel on &lt;a href=&quot;http://freenode.net&quot;&gt;Freenode&lt;/a&gt;, as usual, so feel free to come by and join us!&lt;/p&gt;
</content:encoded></item><item><title>Logitech Unifying devices support in UPower</title><link>https://julien.danjou.info/blog/logitech-unifying-upower/</link><guid isPermaLink="true">https://julien.danjou.info/blog/logitech-unifying-upower/</guid><description>A few months ago, I wrote about my reverse engineering attempt to Logitech Unifying devices . Back then, I concluded my post with big hopes on the future after receiving a document with some part of t</description><pubDate>Fri, 16 Nov 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few months ago, &lt;a href=&quot;https://julien.danjou.info/blog/logitech-k750-linux-support&quot;&gt;I wrote about my reverse engineering attempt to Logitech Unifying devices&lt;/a&gt;. Back then, I concluded my post with big hopes on the future after receiving a document with some part of the specification of the HID++ 2.0 from Logitech.&lt;/p&gt;
&lt;p&gt;A couple of weeks ago, some of my summer work has been merged to &lt;a href=&quot;http://upower.freedesktop.org/&quot;&gt;UPower&lt;/a&gt;, adding battery support for some Logitech devices.&lt;/p&gt;
&lt;h2&gt;HID++&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/m705.jpg&quot; alt=&quot;m705&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As I discovered late in my first reverse engineering attempt, Logitech developed a custom HID protocol named HID++. This protocol exists in two versions, 1.0 and 2.0. Some devices talk with version 1 of the protocol (like my M705 mouse) and some others talk with version 2 of the protocol (like my K750 keyboard).&lt;/p&gt;
&lt;p&gt;Recently, I&apos;ve been able to be in touch with a Logitech engineer who worked on the Linux support for the Unifying receiver, and he has been really helpful and exposed me some details about this protocol.&lt;/p&gt;
&lt;p&gt;Logitech made the decision to publish their HID++ specification publicly about a year ago, but still didn&apos;t do it. The internal review needed to publish such documents hasn&apos;t be done yet. The &lt;a href=&quot;http://6xq.net/git/lars/lshidpp.git/plain/doc/logitech_hidpp_2.0_specification_draft_2012-06-04.pdf&quot;&gt;only published draft&lt;/a&gt; is just an extract of the specification, with even some typo in it as I discovered.&lt;/p&gt;
&lt;p&gt;Some &lt;a href=&quot;https://drive.google.com/?tab=mo&amp;amp;pli=1&amp;amp;authuser=0#folders/0BxbRzx7vEV7eWmgwazJ3NUFfQ28&quot;&gt;other documents&lt;/a&gt; have been recently published, but I didn&apos;t have the time to review them. They contains HID++ 1.0 specifications and some details I asked for about the K750 keyboard.&lt;/p&gt;
&lt;h2&gt;UPower support&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/upower-1.png&quot; alt=&quot;upower-1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It took me sometime to get a full understanding of the protocol, its different version etc. After reverse engineering my K750 keyboard, I&apos;ve also reverse engineered the data stream used to get my M705 mouse battery status. I&apos;ve also received some information about the HID++ 1.0 protocol, so I&apos;ve been able to discover a bit more on what the packets mean. Most of my discoveries are now used to do proper &lt;code&gt;#define&lt;/code&gt; in &lt;a href=&quot;http://cgit.freedesktop.org/upower/tree/src/linux/up-device-lg-unifying.c&quot;&gt;&lt;code&gt;up-lg-unifying.c&lt;/code&gt;&lt;/a&gt; so the code makes more sense.&lt;/p&gt;
&lt;p&gt;My &lt;a href=&quot;http://cgit.freedesktop.org/upower/commit/?id=2f03ad62b520fc5c02e3ff9eb5bffc4275eb88dc&quot;&gt;first patch&lt;/a&gt; implements a new property for UPower devices, named &lt;em&gt;luminosity&lt;/em&gt;, that use with K750 keyboard to report the light level received. The &lt;a href=&quot;http://cgit.freedesktop.org/upower/commit/?id=95184593504bca5240ecd296db98954decd2c5a5&quot;&gt;second patch&lt;/a&gt; add support for Logitech Unifying devices (over USB only) and should work with at least Logitech M705 and K750 devices. This should be available with the next version of UPower, which should be 0.9.19.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnome-power-statistics-k750.png&quot; alt=&quot;gnome-power-statistics-k750&quot; /&gt;&lt;/p&gt;
&lt;p&gt;So far, Logitech has been kind enough to help me understanding part of the protocol and even sent me a few devices so I can play and test my work with them. Unfortunately, this will probably requires some work and time, and so far Logitech was not able to help with that.&lt;/p&gt;
&lt;p&gt;There should be enough information out there to at least add support for battery to HID++ 2.0 devices, and probably a few other things too. I hope I&apos;d get the time do this at some point, but feel free to beat me in this race!&lt;/p&gt;
</content:encoded></item><item><title>OpenStack France meetup #2</title><link>https://julien.danjou.info/blog/openstack-france-meetup-2/</link><guid isPermaLink="true">https://julien.danjou.info/blog/openstack-france-meetup-2/</guid><description>I was at the OpenStack France meetup 2 yesterday evening.</description><pubDate>Tue, 06 Nov 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I was at the &lt;a href=&quot;http://www.meetup.com/OpenStack-France/events/84177022/&quot;&gt;OpenStack France meetup 2&lt;/a&gt; yesterday evening.&lt;/p&gt;
&lt;p&gt;This has been a wonderful evening, talking about OpenStack and all with around 30-40 people. I and &lt;a href=&quot;http://nicolas.barcet.com/&quot;&gt;Nick Barcet&lt;/a&gt; presented &lt;a href=&quot;http://launchpad.net/ceilometer&quot;&gt;Ceilometer&lt;/a&gt; and have received some good feedbacks about it.&lt;/p&gt;
&lt;p&gt;We should also thanks &lt;a href=&quot;http://www.nebula.com/&quot;&gt;Nebula&lt;/a&gt;, who sponsored the evening, and &lt;a href=&quot;http://erwan.com/&quot;&gt;Erwan Gallen&lt;/a&gt; since it was nicely organized, and free beers are always enjoyable.&lt;/p&gt;
</content:encoded></item><item><title>Inside Synaps, a CloudWatch-like implementation for OpenStack</title><link>https://julien.danjou.info/blog/openstack-synaps-exploration/</link><guid isPermaLink="true">https://julien.danjou.info/blog/openstack-synaps-exploration/</guid><description>A few days ago, Samsung released the source code of Synaps, an implementation of the Amazon Web Service CloudWatch API for OpenStack.</description><pubDate>Mon, 22 Oct 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A few days ago, &lt;a href=&quot;http://www.samsung.com/&quot;&gt;Samsung&lt;/a&gt; released the source code of &lt;a href=&quot;https://github.com/spcs/synaps&quot;&gt;Synaps&lt;/a&gt;, an implementation of the &lt;a href=&quot;http://aws.amazon.com/cloudwatch/&quot;&gt;Amazon Web Service CloudWatch API&lt;/a&gt; for &lt;a href=&quot;http://openstack.org&quot;&gt;OpenStack&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Being a developer on the &lt;a href=&quot;http://launchpad.net/ceilometer&quot;&gt;Ceilometer&lt;/a&gt; project, I&apos;ve been curious to look on this project and how it could overlap with Ceilometer or other projects like &lt;a href=&quot;http://www.heat-api.org&quot;&gt;Heat&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;What is CloudWatch?&lt;/h2&gt;
&lt;p&gt;CloudWatch is a monitoring system provided by Amazon on its Web Services platform to monitor services. This allows you get notifications and trigger an action on certain threshold.&lt;/p&gt;
&lt;p&gt;For example, this can be used to scale your architecture by monitoring the number of requests you get on it and its general load by starting new servers.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/cloudwatch.jpg&quot; alt=&quot;cloudwatch&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Synaps&lt;/h2&gt;
&lt;p&gt;Synaps is written in around 7k lines of Python (with 28 % of which are comments), reuses at least one common module of OpenStack (&lt;em&gt;openstack.common.cfg&lt;/em&gt;) and copy some modules from Nova. One thing that strikes me, is that there seems to be only a few unit tests compared to most OpenStack projects. Also, many parts of the code and documentation contains text written in korean, which won&apos;t be very helpful for most people! :-) It uses some external technologies, like &lt;a href=&quot;http://storm-project.net/&quot;&gt;Storm&lt;/a&gt;, &lt;a href=&quot;http://cassandra.apache.org/&quot;&gt;Cassandra&lt;/a&gt; to store its persistent data and &lt;a href=&quot;http://pandas.pydata.org/&quot;&gt;Pandas&lt;/a&gt; to do data analysis.&lt;/p&gt;
&lt;p&gt;The API server provides an EC2 compatible API only: no OpenStack specific API. This is probably not a bad thing for now, since I am not aware of any work in this direction. The API access directly the Cassandra back-end for read operation, but relies on RPC to do writes. This way, a set of daemon handles the write using the Storm part of Synaps and do data aggregation. The authentication only supports LDAP, but it should still be possible to add a driver for Keystone.&lt;/p&gt;
&lt;p&gt;A Java and a Python SDK are provided to record metrics into Synaps, but&lt;br /&gt;
there&apos;s not enough documentation for it to be useful.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/SynapsDeployment-1.jpg&quot; alt=&quot;SynapsDeployment-1&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Overlap with Heat&lt;/h2&gt;
&lt;p&gt;For now, there&apos;s not a lot of overlap with Heat, because Heat does not implement completely the CloudWatch API. Heat actually still misses a lot of the CloudWatch functions. But as soon as it will implement the CloudWatch API completely, the overlap will be complete with Synaps in this regard.&lt;/p&gt;
&lt;p&gt;One divergence point however, is that Heat uses RPC to access data from the storage back-end via its engine (the central daemon), whereas Synaps directly connects to Cassandra. Also, Heat relies on SQLAlchemy, like most OpenStack projects needing a database.&lt;/p&gt;
&lt;h2&gt;Overlap with Ceilometer&lt;/h2&gt;
&lt;p&gt;One of the goal of Ceilometer is to provide data probes and pollsters for all OpenStack components (Nova, Swift, Quantum…) whereas Synaps let the OpenStack users to put any kind of metric inside it, and therefore doesn&apos;t provide anything for now.&lt;/p&gt;
&lt;p&gt;But the storage of metrics is the main common point between Synaps and Ceilometer. Synaps chose only one technology, Cassandra, to store its metrics, whereas Ceilometer took care of building an abstraction layer for the storage engine. Ceilometer currently allows an operator to use SQL or MongoDB, but Cassandra could likely be added.&lt;/p&gt;
&lt;p&gt;Data metric consolidation is done by Synaps. This makes sense, since Synaps don&apos;t need to have the full data history to trigger alarms. On the opposite, Ceilometer needs to have a full history to allow things like billing, and don&apos;t do any aggregation on data.&lt;/p&gt;
&lt;p&gt;Also, in Synaps, the data analysis is done using Pandas. This means the data used are retrieved from the Cassandra back-end, and then transformed by Pandas inside Synaps in something else. It&apos;s likely that in such a case, Synaps should use CQL to achieve that. Ceilometer manipulates the data near their storage: it means that the computation are done by back-end to be efficient (SQL, mapreduce…).&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Considering Samsung open-sourced Synaps late in the development process, I don&apos;t feel like they aimed to have it becoming a core component. This is always sad, because the effort put into this implementation are big and it would have probably little to add some abstraction layers to follow what other OpenStack projects do. But this takes time and energy, and it&apos;s understandable that Samsung didn&apos;t want to achieve this in a short time frame.&lt;/p&gt;
&lt;p&gt;There&apos;s a part of the code and architecture that overlaps with Ceilometer and Heat. Ceilometer is becoming a specialized point to store data metrics from any source: so it&apos;s sad, but understandable, that Synaps did not tried to reuse it. Fortunately, Heat is working with Ceilometer to achieve exactly that. This means OpenStack would have only one metrics storage point, used for billing, for monitoring and alarming.&lt;/p&gt;
&lt;p&gt;Therefore, I think Synaps is an implementation of CloudWatch that should be looked at as an inspiration for Heat and Ceilometer to build a better and more integrated solution!&lt;/p&gt;
</content:encoded></item><item><title>Ceilometer 0.1 released</title><link>https://julien.danjou.info/blog/ceilometer-0-1-released/</link><guid isPermaLink="true">https://julien.danjou.info/blog/ceilometer-0-1-released/</guid><description>After 6 months of development, we are proud to release the first release of Ceilometer, the OpenStack Metering project. Ceilometer.</description><pubDate>Fri, 12 Oct 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;After 6 months of development, we are proud to release the first release of &lt;a href=&quot;http://launchpad.net/ceilometer&quot;&gt;Ceilometer&lt;/a&gt;, the &lt;a href=&quot;http://openstack.org&quot;&gt;OpenStack&lt;/a&gt; Metering project. Ceilometer. This is a first and amazing milestone for us: we follow all other projects by releasing a version for Folsom!&lt;/p&gt;
&lt;p&gt;Using Ceilometer, you should now be able to meter your OpenStack cloud and retrieve its usage to build statistics or bill your customer!&lt;/p&gt;
&lt;p&gt;You can read &lt;a href=&quot;https://lists.launchpad.net/openstack/msg17410.html&quot;&gt;our announcement on the OpenStack mailing list&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Architecture&lt;/h2&gt;
&lt;p&gt;We spent a good amount of time defining and refining &lt;a href=&quot;http://ceilometer.readthedocs.org/en/latest/architecture.html#high-level-description&quot;&gt;our architecture&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/Ceilometer_Architecture-1.png&quot; alt=&quot;Ceilometer_Architecture-1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;One of its important point, is that it has been designed to work without modifying any of the existing core components. Patching OpenStack components in an intrusive way to meter them was not an option for now, simply because we had no legitimacy to do so. This may change in the future, and this will likely be discussed next week during the &lt;a href=&quot;http://www.openstack.org/summit/san-diego-2012/&quot;&gt;OpenStack Summit&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Meters&lt;/h2&gt;
&lt;p&gt;Initially, we defined a bunch of meters we&apos;d like to have for a first release, and in the end, most of them are available. Some of them are still missing, like OpenStack Object Storage (Swift) ones, mainly due to lack of interest from the involved parties so far.&lt;/p&gt;
&lt;p&gt;Anyhow, with this first release, you should be able to meter your instances, their network usage, memory, CPU. Images, networks and volumes and their CRUD operations are metered too. For more detail, you can read the &lt;a href=&quot;http://ceilometer.readthedocs.org/en/latest/measurements.html&quot;&gt;complete list of implemented meters&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;REST API&lt;/h2&gt;
&lt;p&gt;The HTTP REST API has been partially implemented. The provided methods should allow basic integration with a billing system.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://dreamhost.com/&quot;&gt;DreamHost&lt;/a&gt; is using Ceilometer in their deployment architecture and coupling it with their billing system!&lt;/p&gt;
&lt;h2&gt;Towards Grizzly&lt;/h2&gt;
&lt;p&gt;We don&apos;t have a clear and established road-map for Grizzly yet.&lt;/p&gt;
&lt;p&gt;We already have a couple of patches waiting in the queue to be merged, like the use of &lt;a href=&quot;https://review.openstack.org/#/c/13989/&quot;&gt;Keystone to authenticate API request&lt;/a&gt; and the &lt;a href=&quot;https://review.openstack.org/#/c/14185/&quot;&gt;removal of Nova DB access&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;On my side, these last days I&apos;ve been working on a small debug user interface for the API. Ceilometer API server will return this interface if your do an API request from a browser (i.e. requesting &lt;code&gt;text/html&lt;/code&gt; instead of &lt;code&gt;application/json&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/ceilometer-debug-interface.png&quot; alt=&quot;ceilometer-debug-interface&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I hope this will help to discover Ceilometer API more easily for new comers and leverage it to build powerful tools!&lt;/p&gt;
&lt;p&gt;Anyhow, we have tons of idea and work to do, and I&apos;m sure the upcoming weeks will be very interesting. Also, we hope to be able to become an OpenStack incubated project soon. So stay tuned!&lt;/p&gt;
</content:encoded></item><item><title>Gnus notifications</title><link>https://julien.danjou.info/blog/gnus-notifications/</link><guid isPermaLink="true">https://julien.danjou.info/blog/gnus-notifications/</guid><description>Today, I&apos;ve merged my Gnus notifications module inside Gnus git repository. This way, it will be available for everybody in Emacs 24.2.</description><pubDate>Wed, 29 Aug 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Today, &lt;a href=&quot;http://git.gnus.org/cgit/gnus.git/commit/?id=7b7db76666fae115c7ec0cc78ca96ea4e177ba4e&quot;&gt;I&apos;ve merged my Gnus notifications module&lt;/a&gt; inside Gnus git repository. This way, it will be available for everybody in Emacs 24.2.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnus-notifications-1.png&quot; alt=&quot;gnus-notifications-1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This module allows you to be notified via &lt;code&gt;notifications-notify&lt;/code&gt; (the Emacs implementation of the &lt;a href=&quot;http://www.galago-project.org/specs/notification/&quot;&gt;Freedesktop desktop notifications&lt;/a&gt;) on new messages received in Gnus. It can also retrieves contacts photo via &lt;em&gt;gravatar.el&lt;/em&gt; and &lt;em&gt;google-contacts.el&lt;/em&gt; to include them in the notification.&lt;/p&gt;
&lt;p&gt;To enable it in Emacs &amp;gt; 24.1, you just have to add the following line to your Gnus configuration file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(add-hook &apos;gnus-after-getting-new-news-hook &apos;gnus-notifications)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want to download it and use it stand-alone for a previous Emacs version, you can &lt;a href=&quot;http://git.gnus.org/cgit/gnus.git/plain/lisp/gnus-notifications.el&quot;&gt;fetch the latest file revision&lt;/a&gt; and load it before adding the previously given line.&lt;/p&gt;
</content:encoded></item><item><title>Sony Vaio Z Debian Linux support</title><link>https://julien.danjou.info/blog/sony-vaio-svz13-linux/</link><guid isPermaLink="true">https://julien.danjou.info/blog/sony-vaio-svz13-linux/</guid><description>I had to install Debian Wheezy on a brand new Sony Vaio Z laptop with the new Ivy Bridge architecture (SVZ1311C5E). I&apos;ll talk about this here, because it&apos;s always nice to know that new hardware works</description><pubDate>Sat, 11 Aug 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I had to install Debian Wheezy on a brand new Sony Vaio Z laptop with the new Ivy Bridge architecture (SVZ1311C5E). I&apos;ll talk about this here, because it&apos;s always nice to know that new hardware works quite fine (or not) under Debian.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/sony-vaio-z-2012-1.jpg&quot; alt=&quot;sony-vaio-z-2012-1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The laptop is delivered with Window 7, which I decided to remove entirely anyway, and replace with Debian. I&apos;ve installed it with Linux 3.2 and then ran Linux 3.4, 3.5 and 3.6-rc1.&lt;/p&gt;
&lt;h2&gt;USB booting&lt;/h2&gt;
&lt;p&gt;Don&apos;t ask me why, nor an Ubuntu or Debian USB installation booted, blocked at SYSLINUX at best, or at a black screen. This does not work. I had to use PXE to install Debian.&lt;/p&gt;
&lt;h2&gt;Storage&lt;/h2&gt;
&lt;p&gt;The only thing that can be surprising, is that the 128 GB SSD storage is actually made of 2 64 GB Samsung SSD aggregated in a RAID 0 using &lt;em&gt;&lt;a href=&quot;http://www.intel.com/p/en_US/support/highlights/chpsts/imsm/&quot;&gt;Intel Rapid Storage Technology&lt;/a&gt;&lt;/em&gt;, previously known as &lt;em&gt;Intel Matrix&lt;/em&gt;. This is supported by Linux using the &lt;em&gt;dm-raid&lt;/em&gt; module. So this is a fake RAID, and you anyway can see the both drives as &lt;em&gt;sda&lt;/em&gt; and &lt;em&gt;sdb&lt;/em&gt; under Linux.&lt;/p&gt;
&lt;p&gt;Unfortunately, this kind of RAID is not supported correctly by GRUB, and I was unable to install it this way. Therefore, I decided to remove entirely this fake RAID (which is possible via the BIOS) and use a Linux software &lt;em&gt;md&lt;/em&gt; RAID 0 instead, plus crypto on top of it. That I know well and I trust. :)&lt;/p&gt;
&lt;h2&gt;Graphics&lt;/h2&gt;
&lt;p&gt;The Intel HD Graphics 4000 works fine. I&apos;m alsmo using the HDMI output, which works fine. There&apos;s some GPU hanging (as seen on screen and in kernel logs) in Linux up to 3.4, but with versions 3.5 and above, I didn&apos;t see any problem so far.&lt;/p&gt;
&lt;h2&gt;Sound&lt;/h2&gt;
&lt;p&gt;The Intel HDA sound card works pretty well, both for playing and recording. The main problem is that I hear a constant noise on the speakers, but tweaking the ALSA mixers ends it at some point. There&apos;s still probably a bug, not yet resolved in Linux 3.6-rc1.&lt;/p&gt;
&lt;h2&gt;Keyboard&lt;/h2&gt;
&lt;p&gt;The keyboard works fine, and the back-light too, via the &lt;em&gt;sony-laptop&lt;/em&gt; kernel module. Wonderful.&lt;/p&gt;
&lt;h2&gt;Touchpad&lt;/h2&gt;
&lt;p&gt;Touchpad works fine.&lt;/p&gt;
&lt;h2&gt;Fingerprint&lt;/h2&gt;
&lt;p&gt;It does not work, and is not supported according to my research. Not that I care about, but don&apos;t count on it. It&apos;s an AuthenTec AES1660.&lt;/p&gt;
&lt;h2&gt;Webcam&lt;/h2&gt;
&lt;p&gt;It works perfectly.&lt;/p&gt;
&lt;h2&gt;USB&lt;/h2&gt;
&lt;p&gt;Well, USB 3.0 does not work. I had to disable XHCI in the BIOS and use the 2 ports as standard USB 2.0, otherwise I would just get errors from the kernel. Still not working with Linux 3.6-rc1, and I&apos;ve no clue to debug, and do not use USB 3.0 yet, so…&lt;/p&gt;
&lt;h2&gt;WiFi&lt;/h2&gt;
&lt;p&gt;The WiFi module (based on iwlwifi) works fine. The only problem with NetworkManager is that the &lt;em&gt;sony-laptop&lt;/em&gt; offers a second rfkill switch and NM does not know how to handle it correctly. &lt;a href=&quot;https://bugzilla.gnome.org/show_bug.cgi?id=680632&quot;&gt;A bug is opened about this&lt;/a&gt; and I hope to be able to write a patch or something at some point. Also, there seems to be some quality issue with the &lt;em&gt;iwlwifi&lt;/em&gt; driver and 802.11n at this point. I&apos;m losing connection quite often when the signal drops below 40 %. Loading the module with &lt;em&gt;11n_disable=1&lt;/em&gt; helps a lot.&lt;/p&gt;
&lt;h2&gt;Ethernet&lt;/h2&gt;
&lt;p&gt;The gigabit Realtek Ethernet controller works perfectly.&lt;/p&gt;
&lt;h2&gt;Card reader&lt;/h2&gt;
&lt;p&gt;Works perfectly.&lt;/p&gt;
</content:encoded></item><item><title>Ceilometer, the OpenStack metering project</title><link>https://julien.danjou.info/blog/openstack-metering-ceilometer/</link><guid isPermaLink="true">https://julien.danjou.info/blog/openstack-metering-ceilometer/</guid><description>For the last months, I&apos;ve been working on a metering project for OpenStack, so it&apos;s time to talk a bit about it.</description><pubDate>Fri, 27 Jul 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;For the last months, I&apos;ve been working on a metering project for &lt;a href=&quot;http://openstack.org&quot;&gt;OpenStack&lt;/a&gt;, so it&apos;s time to talk a bit about it.&lt;/p&gt;
&lt;p&gt;OpenStack is a growing cloud platform providing IaaS. A problem easily identified by everyone building a public cloud platform is that nothing is provided to retrieve the platform usage data. Some data are available in some places, but not everything is, and you have to do a lot of processing from the various components to get something useful in the end. But in order to bill customers that are using your public cloud platform, you need to do his.&lt;/p&gt;
&lt;p&gt;In this regard, a lot of companies running public OpenStack based infrastructure wrote their own solution to cover this functional areas, and to become able to bill theirs customers.&lt;/p&gt;
&lt;p&gt;To avoid everybody doing and maintaining such a stack in their corners, the &lt;a href=&quot;http://launchpad.net/ceilometer&quot;&gt;Ceilometer&lt;/a&gt; has been created.&lt;/p&gt;
&lt;p&gt;The project aims to cover the metering aspect of the OpenStack components, pulling usage data from every components and storing them into a single place. It then offer a retrieving point for this data via a REST API.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;http://wiki.openstack.org/EfficientMetering&quot;&gt;initial specifications&lt;/a&gt; have been written in April this year, and actual implementation started in May. The project is currently worked on by me, Dreamhost and Canonical.&lt;/p&gt;
&lt;p&gt;We already have designed &lt;a href=&quot;http://wiki.openstack.org/EfficientMetering/ArchitectureProposalV1&quot;&gt;an architecture&lt;/a&gt; that we are implementing, and we hope to release a first usable version with Folsom.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/ceilometer-architecture-1.png&quot; alt=&quot;ceilometer-architecture-1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I did a presentation of this project yesterday at &lt;a href=&quot;http://xlcloud.org/&quot;&gt;XLCloud&lt;/a&gt;, which has been very well received.&lt;/p&gt;
&lt;p&gt;If you are interested in helping us and contributing, feel free to join us during one of our &lt;a href=&quot;http://wiki.openstack.org/Meetings/MeteringAgenda&quot;&gt;weekly IRC meeting&lt;/a&gt; or fix &lt;a href=&quot;https://bugs.launchpad.net/ceilometer&quot;&gt;some bugs&lt;/a&gt;. :-)&lt;/p&gt;
</content:encoded></item><item><title>Emacs configuration published</title><link>https://julien.danjou.info/blog/emacs-configuration-published/</link><guid isPermaLink="true">https://julien.danjou.info/blog/emacs-configuration-published/</guid><description>I&apos;ve finally published my Emacs configuration.  This took me a while, since I had personal information inside (like passwords). Recently, I&apos;ve been able to move them away and can now publish everythin</description><pubDate>Tue, 24 Jul 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve finally published my Emacs configuration.&lt;/p&gt;
&lt;p&gt;This took me a while, since I had personal information inside (like passwords). Recently, I&apos;ve been able to move them away and can now publish everything in my &lt;a href=&quot;https://github.com/jd/emacs.d.git&quot;&gt;Git repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It&apos;s probably not yet usable from scratch, since I didn&apos;t include the bootstrap code for &lt;a href=&quot;http://github.com/dimitri/el-get&quot;&gt;el-get&lt;/a&gt;. But you can at least lurk and grab some ideas or lines of code. And do not hesitate to ask me anything about it!&lt;/p&gt;
&lt;p&gt;Note that I&apos;m using Emacs development version (trunk), so it&apos;s possible that some things do not work with (old) released Emacs versions.&lt;/p&gt;
</content:encoded></item><item><title>ERC notifications</title><link>https://julien.danjou.info/blog/erc-notifications/</link><guid isPermaLink="true">https://julien.danjou.info/blog/erc-notifications/</guid><description>Today, I&apos;ve merged my erc notifications module inside Emacs trunk. This way, it will be available for everybody in Emacs 24.2.</description><pubDate>Sat, 21 Jul 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Today, &lt;a href=&quot;http://bzr.savannah.gnu.org/lh/emacs/trunk/revision/109176&quot;&gt;I&apos;ve merged my erc notifications module&lt;/a&gt; inside Emacs trunk. This way, it will be available for everybody in Emacs 24.2.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/erc-notifications-1.png&quot; alt=&quot;erc-notifications-1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This module allows you to be notified via &lt;code&gt;notifications-notify&lt;/code&gt; (the Emacs implementation of the &lt;a href=&quot;http://www.galago-project.org/specs/notification/&quot;&gt;Freedesktop desktop notifications&lt;/a&gt;) on private message received on IRC, or when your nickname is mentioned on a channel.&lt;/p&gt;
&lt;p&gt;To enable it in Emacs &amp;gt; 24.1, you just have to add the following line to your configuration file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(add-to-list &apos;erc-modules &apos;notifications)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you want to download it and use it stand-alone for a previous Emacs version, you can &lt;a href=&quot;http://bzr.savannah.gnu.org/lh/emacs/trunk/annotate/head:/lisp/erc/erc-notifications.el&quot;&gt;fetch the latest file revision&lt;/a&gt; and load it before adding the previously given line.&lt;/p&gt;
</content:encoded></item><item><title>Logitech K750 keyboard and Unifying Receiver Linux support</title><link>https://julien.danjou.info/blog/logitech-k750-linux-support/</link><guid isPermaLink="true">https://julien.danjou.info/blog/logitech-k750-linux-support/</guid><description>A year ago, I bought a Logitech Wireless Solar Keyboard K750. I&apos;m particularly picky on keyboards, but this one is good.</description><pubDate>Mon, 09 Jul 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A year ago, I bought a &lt;a href=&quot;http://www.logitech.com/keyboards/keyboards/k750-keyboard&quot;&gt;Logitech Wireless Solar Keyboard K750&lt;/a&gt;. I&apos;m particularly picky on keyboards, but this one is good. It has an incredible useful feature: while being wireless, it has no need for disposable or rechargeable batteries, it uses solar power!&lt;/p&gt;
&lt;p&gt;My problem is that there&apos;s obviously no way to know the battery status from Linux, the provided application only working on Windows.&lt;/p&gt;
&lt;p&gt;And one dark night, while fragging on QuakeLive, my keyboard stopped working: it had no battery left. This activity being quite energy consuming, it emptied the whole battery.&lt;/p&gt;
&lt;p&gt;Someone should write code to get the battery status and light meter from Linux: challenge accepted!&lt;/p&gt;
&lt;h2&gt;How the keyboard works&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/logitech-unifying.jpg&quot; alt=&quot;logitech-unifying&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This keyboard, like many of the new wireless devices from Logitech, uses the &lt;em&gt;Unifying&lt;/em&gt; interface. It&apos;s an USB receiver that can be attached up to 6 differents devices (mouse, keyboards…). On old Linux kernel, the &lt;em&gt;Unifying&lt;/em&gt; receiver is recognized as only one keyboard and/or one mouse device.&lt;/p&gt;
&lt;p&gt;Recently, a driver called &lt;em&gt;hid-logitech-dj&lt;/em&gt; has been added to the Linux kernel. With this driver, each device attached to the receiver is recognized as one different device.&lt;/p&gt;
&lt;h2&gt;What the Logitech application does&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/logitech-solar-app.png&quot; alt=&quot;logitech-solar-app&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The Logitech application under Windows works that way: you launch it, and it displays the battery charge level. On the keyboard, there&apos;s a special &quot;light&quot; button (up right). When pressed, a LED will light up on the keyboard: green if the keyboard is receiving enough light and is charging, red if the keyboard does not receive enough light and is therefore discharging. Pushing this same button while the application is running will makes the light meter activated: the application will tell you how much &lt;a href=&quot;http://en.wikipedia.org/wiki/Lux&quot;&gt;lux&lt;/a&gt; your keyboard is receiving.&lt;/p&gt;
&lt;h2&gt;Let&apos;s reverse engineer this&lt;/h2&gt;
&lt;p&gt;As far as I know, there&apos;s nothing in the USB HID protocol that handles this kind of functionality (battery status, light meter…) in a standard way. So the first task to accomplish is, unfortunately, to reverse engineer the program.&lt;/p&gt;
&lt;p&gt;I discovered a bit too late that &lt;a href=&quot;http://www.youtube.com/watch?v=jMf55KVDPaE&quot;&gt;Drew Fisher did a good presentation on USB reverse engineering at 28c3&lt;/a&gt;. You might want to take a look at it if you want to reverse engineer on USB. I did not need it, but I learned a few things.&lt;/p&gt;
&lt;p&gt;Anyway, my plan was the following: run the Logitech application inside a virtual machine running Windows, give it direct access to the USB keyboard, and sniff what happens on the USB wire.&lt;/p&gt;
&lt;p&gt;To achieve that, you need a virtual machine emulator that can do USB pass-through. Both &lt;a href=&quot;http://www.linux-kvm.org/page/Main_Page&quot;&gt;KVM&lt;/a&gt; and &lt;a href=&quot;https://www.virtualbox.org/&quot;&gt;VirtualBox&lt;/a&gt; can do that, but VirtualBox works much better with USB and allow hot(un)plugging of devices, so I used it.&lt;/p&gt;
&lt;p&gt;To sniff what happens on the USB, you need to load the &lt;em&gt;usbmon&lt;/em&gt; Linux kernel module. Simply doing &lt;code&gt;modprobe usbmon&lt;/code&gt; will work. You can then use &lt;a href=&quot;http://www.wireshark.org/&quot;&gt;Wireshark&lt;/a&gt; which know how to use &lt;em&gt;usbmon&lt;/em&gt; devices and understand the USB protocol.&lt;/p&gt;
&lt;h3&gt;USB stuff you need to know&lt;/h3&gt;
&lt;p&gt;You don&apos;t need to know much about USB to understand what I&apos;ll write about below, but for the sake of comprehensibility I&apos;ll write a couple of things here before jumping in.&lt;/p&gt;
&lt;p&gt;To communicate with an USB device, we communicate with one of its &lt;em&gt;endpoints&lt;/em&gt;. Endpoints are regrouped into an &lt;em&gt;interface&lt;/em&gt;. Interfaces are regrouped into a &lt;em&gt;configuration&lt;/em&gt;. A device might contains one or several configurations.&lt;/p&gt;
&lt;p&gt;There&apos;s also several types of packets in the USB wire protocol, and at least two of them interest us there, they are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Interrupt packets, a packet send spontaneously;&lt;/li&gt;
&lt;li&gt;Controls packets, used for command and status operations.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of this and more is well (and better) explained in the &lt;a href=&quot;http://lwn.net/images/pdf/LDD3/ch13.pdf&quot;&gt;chapter 13&lt;/a&gt; of &lt;a href=&quot;http://lwn.net/Kernel/LDD3/&quot;&gt;Linux Device Drivers, Third Edition&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Sniffed data&lt;/h3&gt;
&lt;p&gt;Once everything was set-up, I ran my beloved Wireshark. There&apos;s a an URB of type &lt;em&gt;interrupt&lt;/em&gt; sent each time you press any key with some data in it. Therefore I advise you to plug another keyboard (or use the laptop keyboard if you&apos;re doing this on a laptop), otherwise you&apos;ll get crazy trying to sniff the keyboard you&apos;re typing on.&lt;/p&gt;
&lt;p&gt;At this point, just launching the application does a bunch of USB traffic. Pressing the &quot;light&quot; button on the keyboard makes even more USB packets coming in and out. Here&apos;s the interesting packets that I noticed once I excluded the noise:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When pressing the &quot;light&quot; button, an URB of type &lt;em&gt;interrupt&lt;/em&gt; is sent by the keyboard to the computer;&lt;/li&gt;
&lt;li&gt;An URB &lt;em&gt;control&lt;/em&gt; packet is sent by the computer to the keyboard in response;&lt;/li&gt;
&lt;li&gt;Regularly URB &lt;em&gt;interrupt&lt;/em&gt; packets are sent just after.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With all this, the next step was clear: understand the packets and reproduce that exchange under Linux.&lt;/p&gt;
&lt;h3&gt;What the packets mean&lt;/h3&gt;
&lt;h4&gt;The &quot;go for the light meter&quot; packet&lt;/h4&gt;
&lt;p&gt;The packet sent from the computer to the keyboard is the following.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Frame 17: 71 bytes on wire (568 bits), 71 bytes captured (568 bits)
    Frame Length: 71 bytes (568 bits)
    Capture Length: 71 bytes (568 bits)
USB URB
    URB id: 0xffff880161997240
    URB type: URB_SUBMIT (&apos;S&apos;)
    URB transfer type: URB_CONTROL (0x02)
    Endpoint: 0x00, Direction: OUT
        0... .... = Direction: OUT (0)
        .000 0000 = Endpoint value: 0
    Device: 6
    URB bus id: 1
    Device setup request: relevant (0)
    Data: present (0)
    URB sec: 1340124450
    URB usec: 495643
    URB status: Operation now in progress (-EINPROGRESS) (-115)
    URB length [bytes]: 7
    Data length [bytes]: 7
    [Response in: 18]
    [bInterfaceClass: HID (0x03)]
    URB setup
        bmRequestType: 0x21
            0... .... = Direction: Host-to-device
            .01. .... = Type: Class (0x01)
            ...0 0001 = Recipient: Interface (0x01)
    bRequest: SET_REPORT (0x09)
    wValue: 0x0210
        ReportID: 16
        ReportType: Output (2)
    wIndex: 2
    wLength: 7
0000  40 72 99 61 01 88 ff ff 53 02 00 06 01 00 00 00   @r.a....S.......
0010  22 ad e0 4f 00 00 00 00 1b 90 07 00 8d ff ff ff   &quot;..O............
0020  07 00 00 00 07 00 00 00 21 09 10 02 02 00 07 00   ........!.......
0030  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
0040  10 01 09 03 78 01 00                              ....x..
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What&apos;s here interesting is the last part representing the data. &lt;em&gt;wLength&lt;/em&gt; says that the length of the data is 7 bytes, so let&apos;s take a look at those 7 bytes: &lt;code&gt;10 01 09 03 78 01 00&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Well, actually, you can&apos;t decode them like that, unless you&apos;re a freak or a Logitech engineer. And I have actually no idea what they mean. But sending this to the keyboard will trigger an interesting thing: the keyboard will start sending URB interrupt with some data without you pressing any more key.&lt;/p&gt;
&lt;h4&gt;The &quot;light meter and battery values&quot; packet&lt;/h4&gt;
&lt;p&gt;This is most interesting packet. This is the one sent by the keyboard to the host and that contains the data we want to retrieve.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Frame 1467: 84 bytes on wire (672 bits), 84 bytes captured (672 bits)
    Frame Length: 84 bytes (672 bits)
    Capture Length: 84 bytes (672 bits)
USB URB
    URB id: 0xffff88010c43c380
    URB type: URB_COMPLETE (&apos;C&apos;)
    URB transfer type: URB_INTERRUPT (0x01)
    Endpoint: 0x83, Direction: IN
        1... .... = Direction: IN (1)
        .000 0011 = Endpoint value: 3
    Device: 2
    URB bus id: 6
    Device setup request: not relevant (&apos;-&apos;)
    Data: present (0)
    URB sec: 1334953309
    URB usec: 728740
    URB status: Success (0)
    URB length [bytes]: 20
    Data length [bytes]: 20
    [Request in: 1466]
    [Time from request: 0.992374000 seconds]
    [bInterfaceClass: Unknown (0xffff)]
Leftover Capture Data: 1102091039000c061d474f4f4400000000000000

0000  80 c3 43 0c 01 88 ff ff 43 01 83 02 06 00 2d 00   ..C.....C.....-.
0010  5d c5 91 4f 00 00 00 00 a4 1e 0b 00 00 00 00 00   ]..O............
0020  14 00 00 00 14 00 00 00 00 00 00 00 00 00 00 00   ................
0030  02 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00   ................
0040  11 02 09 10 39 00 0c 06 1d 47 4f 4f 44 00 00 00   ....9....GOOD...
0050  00 00 00 00                                       ....
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This packets come in regularly (1 per second) on the wire for some time once you sent the &quot;go for the light meter&quot; packet. At one point they are emitted less often and do not contain the value for the light meter anymore, suggesting that the control packet sent earlier triggers the activation of the light meter for a defined period.&lt;/p&gt;
&lt;p&gt;Now you probably wonder where the data are in this. They&apos;re in the 20 bytes leftover in the capture data part, indicated by Wireshark, at the end of the packet: &lt;code&gt;11 02 09 10 39 00 0c 06 1d 47 4f 4f 44 00 00 00 00 00 00 00&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Fortunately, it was easy to decode. Knowing we&apos;re looking for 2 values (battery charge and light meter), we just need to observe and compare the packet emitted on the wire with the values displayed by the Logitech Solar App.&lt;/p&gt;
&lt;p&gt;To achieve this, I looked both at the &lt;em&gt;Logitech Solar App&lt;/em&gt; and &lt;em&gt;Wireshark&lt;/em&gt; while bringing more and more light near the keyboard, increasing the lux value received by the meter on the Solar App, and saw that the fields represented in blue (see below) where changing in Wireshark. Since 2 bytes were changing, I guessed that it was coded on 16 bits, and therefore it was easy to correlate the value with the Solar App.&lt;/p&gt;
&lt;p&gt;[ ....9....GOOD....... ]
11 02 09 10 39 00 0c 06 1d 47 4f 4f 44 00 00 00 00 00 00 00
4 bytes - 1 byte for battery charge - 2 bytes for light meter - 2 bytes - 4 bytes for GOOD - 7 bytes&lt;/p&gt;
&lt;p&gt;In this example, the battery has a charge of &lt;code&gt;0x39 = 57 %&lt;/code&gt; and the light meter receives &lt;code&gt;0x0c = 12 lux&lt;/code&gt; of light. It&apos;s basically dark, and that makes sense: it was night and the light was off in my office, the only light being the one coming from my screen.&lt;/p&gt;
&lt;p&gt;I&apos;ve no idea what the &lt;code&gt;GOOD&lt;/code&gt; part of the packet is about, but it&apos;s present in every packet and it&apos;s actually very handy to recognize such a packet. Therefore I&apos;m considering this as some sort of useful mark for now.&lt;/p&gt;
&lt;p&gt;For the other bytes, they were always the same (&lt;code&gt;0x11 0x2 0x9 0x10&lt;/code&gt; at the beginning, 7 times &lt;code&gt;0x00&lt;/code&gt; at the end). The 2 bytes between the light meter and GOOD probably mean something, but I&apos;ve no idea what for now.&lt;/p&gt;
&lt;h2&gt;Building our solar app&lt;/h2&gt;
&lt;p&gt;Now we&apos;ve enough information to build our own very basic solar application. We know how to triggers the light meter, and we know how to decode the packets.&lt;/p&gt;
&lt;p&gt;We&apos;re going to write a small application using &lt;a href=&quot;http://www.libusb.org/&quot;&gt;libusb&lt;/a&gt;. Here&apos;s a quick example. It&apos;s not perfect and does not check for error codes, be careful.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* Written by Julien Danjou &amp;lt;julien@danjou.info&amp;gt; in 2012 */

#include &amp;lt;linux/hid.h&amp;gt;

#include &amp;lt;libusb.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;

int main(void)
{
    libusb_context *ctx;
    libusb_init(&amp;amp;ctx);
    libusb_set_debug(ctx, 3);

    /* Look at the keyboard based on vendor and device id */
    libusb_device_handle *device_handle = libusb_open_device_with_vid_pid(ctx, 0x046d, 0xc52b);

    fprintf(stderr, &quot;Found keyboard 0x%p\n&quot;, device_handle);

    libusb_device *device = libusb_get_device(device_handle);

    struct libusb_device_descriptor desc;

    libusb_get_device_descriptor(device, &amp;amp;desc);

    for(uint8_t config_index = 0; config_index &amp;lt; desc.bNumConfigurations; config_index++)
    {
        struct libusb_config_descriptor *config;

        libusb_get_config_descriptor(device, config_index, &amp;amp;config);

        /* We know we want interface 2 */
        int iface_index = 2;
        const struct libusb_interface *iface = &amp;amp;config-&amp;gt;interface[iface_index];

        for (int altsetting_index = 0; altsetting_index &amp;lt; iface-&amp;gt;num_altsetting; altsetting_index++)
        {
            const struct libusb_interface_descriptor *iface_desc = &amp;amp;iface-&amp;gt;altsetting[altsetting_index];

            if (iface_desc-&amp;gt;bInterfaceClass == LIBUSB_CLASS_HID)
            {
                libusb_detach_kernel_driver(device_handle, iface_index);
                libusb_claim_interface(device_handle, iface_index);

                unsigned char ret[65535];

                unsigned char payload[] = &quot;\x10\x01\x09\x03\x78\x01\x00&quot;;

                if(libusb_control_transfer(device_handle,
                                           LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE,
                                           HID_REQ_SET_REPORT,
                                           0x0210, iface_index, payload, sizeof(payload) - 1, 10000))
                {
                    int actual_length = 0;

                    while(actual_length != 20 || strncmp((const char *) &amp;amp;ret[9], &quot;GOOD&quot;, 4))
                        libusb_interrupt_transfer(device_handle,
                                                  iface_desc-&amp;gt;endpoint[0].bEndpointAddress,
                                                  ret, sizeof(ret), &amp;amp;actual_length, 100000);

                    uint16_t lux = ret[5] &amp;lt;&amp;lt; 8 | ret[6];

                    fprintf(stderr, &quot;Charge: %d %%\nLight: %d lux\n&quot;, ret[4], lux);
                }

                libusb_release_interface(device_handle, iface_index);
                libusb_attach_kernel_driver(device_handle, iface_index);
            }
        }
    }

    libusb_close(device_handle);
    libusb_exit(ctx);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What the program is doing is the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Request for the Unifying Receiver device based on vendor and product ID&lt;/li&gt;
&lt;li&gt;Get the HID interface&lt;/li&gt;
&lt;li&gt;Detach the HID interface from the kernel driver&lt;/li&gt;
&lt;li&gt;Claim the interface&lt;/li&gt;
&lt;li&gt;Send a control packets, were parameters are defined using the same data we captured earlier&lt;/li&gt;
&lt;li&gt;Read interrupt packets coming in until we receive one we recognize (length 20 containing the &quot;GOOD&quot; string)&lt;/li&gt;
&lt;li&gt;Decode the content (battery charge &amp;amp; light meter)&lt;/li&gt;
&lt;li&gt;Release the interface&lt;/li&gt;
&lt;li&gt;Reattach the kernel driver to the interface&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This gives the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Found keyboard 0x0x24ec8e0
Charge: 64 %
Light: 21 lux
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Challenge accomplished!&lt;/p&gt;
&lt;h2&gt;To be continued&lt;/h2&gt;
&lt;p&gt;Unfortunately, this approach has at least one major drawback. We have to disconnect the &lt;em&gt;Logitech Unifying Receiver&lt;/em&gt; from the kernel. That means that while we&apos;re waiting for the packet, we&apos;re dropping packets corresponding to other events from every connected device (key presses, pointer motions…).&lt;/p&gt;
&lt;p&gt;In order to solve that, I sent a request for help on the &lt;a href=&quot;http://vger.kernel.org/vger-lists.html#linux-input&quot;&gt;linux-input&lt;/a&gt; mailing list. That way, I learned that Logitech is actually using the HID++ protocol to communicate with the devices using the Unifying Receiver. &lt;a href=&quot;http://6xq.net&quot;&gt;Lars-Dominik Braun&lt;/a&gt; managed to get the HID++ specifications from Logitech and &lt;a href=&quot;http://6xq.net/git/lars/lshidpp.git/plain/doc/logitech_hidpp_2.0_specification_draft_2012-06-04.pdf&quot;&gt;published them&lt;/a&gt; with their authorization.&lt;/p&gt;
&lt;p&gt;This opens a whole new world. With that document, I may be able to understand the part I reverse engineered and convert this to a more useful and generic library using the hidraw interface (so we don&apos;t have to disconnect the devices from the kernel driver).&lt;/p&gt;
</content:encoded></item><item><title>Making the jump: working freelance</title><link>https://julien.danjou.info/blog/making-the-jump/</link><guid isPermaLink="true">https://julien.danjou.info/blog/making-the-jump/</guid><description>For the last 10 years, I&apos;ve been working on many Free Software projects. From Debian to OpenStack, through awesome, Emacs, XCB and many more.</description><pubDate>Mon, 02 Jul 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;For the last 10 years, I&apos;ve been working on many Free Software projects. From &lt;a href=&quot;http://www.debian.org&quot;&gt;Debian&lt;/a&gt; to &lt;a href=&quot;http://openstack.org&quot;&gt;OpenStack&lt;/a&gt;, through &lt;a href=&quot;http://awesome.naquadah.org&quot;&gt;awesome&lt;/a&gt;, &lt;a href=&quot;http://www.gnu.org/software/emacs/&quot;&gt;Emacs&lt;/a&gt;, &lt;a href=&quot;http://xcb.freedesktop.org&quot;&gt;XCB&lt;/a&gt; and many more. This obviously allowed me to enhance my technical skills, but it also taught me about Free Software and Open Source development processes, and how to work with and close to the community.&lt;/p&gt;
&lt;p&gt;Working for almost 6 years at &lt;a href=&quot;http://easter-eggs.com&quot;&gt;Easter-eggs&lt;/a&gt; taught me how to work in an autonomous manner, how to lead and manage a project. And how to run a company, thanks to the cooperative status of this great one.&lt;/p&gt;
&lt;p&gt;These are the reasons why I decided to leave my latest job and run my own company to work as a freelance consultant &amp;amp; developer specialized in Free Software, starting today.&lt;/p&gt;
&lt;p&gt;Therefore, I am now able and available to provide expertise and development on Free Software, including upstream contribution. Especially on projects I already worked on recently, like &lt;a href=&quot;http://www.openstack.org&quot;&gt;OpenStack&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>How to make Twitter&apos;s Bootstrap tabs bookmarkable</title><link>https://julien.danjou.info/blog/twitter-bootstrap-tabs-bookmark/</link><guid isPermaLink="true">https://julien.danjou.info/blog/twitter-bootstrap-tabs-bookmark/</guid><description>I&apos;ve been using Twitter&apos;s bootstrap library recently to build this Web site, and wondered how to be able to use the bootstrap-tab Javascript plugin in a bookmark friendly manner.</description><pubDate>Fri, 29 Jun 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve been using &lt;a href=&quot;http://twitter.github.com/bootstrap/&quot;&gt;Twitter&apos;s bootstrap&lt;/a&gt; library recently to build this Web site, and wondered how to be able to use &lt;a href=&quot;http://twitter.github.com/bootstrap/javascript.html#tabs&quot;&gt;the bootstrap-tab&lt;/a&gt; Javascript plugin in a bookmark friendly manner.&lt;/p&gt;
&lt;p&gt;I ended up with a simple solution. These are my first steps in Javascript and front-end manipulation, and it&apos;s really not my area of expertise, so don&apos;t be harsh.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function bootstrap_tab_bookmark (selector) { if (selector == undefined) {
    selector = &quot;&quot;; }

    /* Automagically jump on good tab based on anchor */
    $(document).ready(function() {
        url = document.location.href.split(&apos;#&apos;);
        if(url[1] != undefined) {
            $(selector + &apos;[href=#&apos;+url[1]+&apos;]&apos;).tab(&apos;show&apos;);
        }
    });

    var update_location = function (event) {
        document.location.hash = this.getAttribute(&quot;href&quot;);
    }

    /* Update hash based on tab */
    $(selector + &quot;[data-toggle=pill]&quot;).click(update_location);
    $(selector + &quot;[data-toggle=tab]&quot;).click(update_location);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All you need is to use and call this function with a selector (only useful if you have several tabs/pills divisions) when the document is ready.&lt;/p&gt;
&lt;p&gt;The first part takes care of showing the good tab based on the hash contained in the URL. The second part takes care of changing the document location to add the current tab to it when the user clicks.&lt;/p&gt;
</content:encoded></item><item><title>OpenStack Swift eventual consistency analysis &amp; bottlenecks</title><link>https://julien.danjou.info/blog/openstack-swift-consistency-analysis/</link><guid isPermaLink="true">https://julien.danjou.info/blog/openstack-swift-consistency-analysis/</guid><description>Swift is the software behind the OpenStack Object Storage service.</description><pubDate>Mon, 23 Apr 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://launchpad.net/swift&quot;&gt;Swift&lt;/a&gt; is the software behind the &lt;a href=&quot;http://openstack.org/projects/storage/&quot;&gt;OpenStack Object Storage&lt;/a&gt; service.&lt;/p&gt;
&lt;p&gt;This service provides a simple storage service for applications using &lt;a href=&quot;http://docs.openstack.org/api/openstack-object-storage/1.0/content/&quot;&gt;RESTful interfaces&lt;/a&gt;, providing maximum data availability and storage capacity.&lt;/p&gt;
&lt;p&gt;I explain here how some parts of the storage and replication in Swift works, and show some of its current limitations.&lt;/p&gt;
&lt;p&gt;If you don&apos;t know Swift and want to read a more &quot;shallow&quot; overview first, you can read John Dickinson&apos;s &lt;a href=&quot;http://programmerthoughts.com/openstack/swift-tech-overview/&quot;&gt;Swift Tech Overview&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;How Swift storage works&lt;/h2&gt;
&lt;p&gt;If we refer to the &lt;a href=&quot;http://en.wikipedia.org/wiki/CAP_theorem&quot;&gt;CAP theorem&lt;/a&gt;, Swift chose &lt;strong&gt;availability&lt;/strong&gt; and &lt;strong&gt;partition tolerance&lt;/strong&gt; and dropped &lt;strong&gt;consistency&lt;/strong&gt;. That means that you&apos;ll always get your data, they will be dispersed on many places, but you could get an old version of them (or no data at all) in some odd cases (like some server overload or failure). This compromise is made to allow maximum availability and scalability of the storage platform.&lt;/p&gt;
&lt;p&gt;But there are mechanisms built into Swift to minimize the potential data inconsistency window: they are responsible for data replication and consistency.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;http://swift.openstack.org/&quot;&gt;official Swift documentation&lt;/a&gt; explains the internal storage in a certain way, but I&apos;m going to write my own explanation here about this.&lt;/p&gt;
&lt;h3&gt;Consistent hashing&lt;/h3&gt;
&lt;p&gt;Swift uses the principle of &lt;a href=&quot;http://en.wikipedia.org/wiki/Consistent_hashing&quot;&gt;consistent hashing&lt;/a&gt;. It builds what it calls a &lt;em&gt;ring&lt;/em&gt;. A ring represents the space of all possible computed hash values divided in equivalent parts. Each part of this space is called a &lt;em&gt;partition&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The following schema (stolen from the &lt;a href=&quot;http://wiki.basho.com/&quot;&gt;Riak&lt;/a&gt; project) shows the principle nicely:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/riak-ring.png&quot; alt=&quot;riak-ring&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In a simple world, if you wanted to store some objects and distribute them on 4 nodes, you would split your hash space in 4. You would have 4 partitions, and computing &lt;em&gt;hash(object) modulo 4&lt;/em&gt; would tell you where to store your object: on node 0, 1, 2 or 3.&lt;/p&gt;
&lt;p&gt;But since you want to be able to extend your storage cluster to more nodes without breaking the whole hash mapping and moving everything around, you need to build a lot more partitions. Let&apos;s say we&apos;re going to build 210 partitions. Since we have 4 nodes, each node will have &lt;code&gt;210 ÷ 4 = 256&lt;/code&gt; partitions. If we ever want to add a 5th node, it&apos;s easy: we just have to re-balance the partitions and move 1⁄4 of the partitions from each node to this 5th node. That means all our nodes will end up with &lt;code&gt;210 ÷ 5 ≈ 204&lt;/code&gt; partitions. We can also define a &lt;em&gt;weight&lt;/em&gt; for each node, in order for some nodes to get more partitions than others.&lt;/p&gt;
&lt;p&gt;With 210 partitions, we can have up to 210 nodes in our cluster. Yeepee.&lt;/p&gt;
&lt;p&gt;For reference, Gregory Holt, one of the Swift authors, also wrote &lt;a href=&quot;http://greg.brim.net/page/building_a_consistent_hashing_ring.html&quot;&gt;an explanation post about the ring&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Concretely, when building one Swift ring, you&apos;ll have to say how much partitions you want, and this is what this value is really about.&lt;/p&gt;
&lt;h3&gt;Data duplication&lt;/h3&gt;
&lt;p&gt;Now, to assure availability and partitioning (as seen in the &lt;em&gt;CAP theorem&lt;/em&gt;) we also want to store replicas of our objects. By default, Swift stores 3 copies of every objects, but that&apos;s configurable.&lt;/p&gt;
&lt;p&gt;In that case, we need to store each partition defined above not only on 1 node, but on 2 others. So Swift adds another concept: zones. A zone is an isolated space that does not depends on other zone, so in case of an outage on a zone, the other zones are still available. Concretely, a zone is likely to be a disk, a server, or a whole cabinet, depending on the size of your cluster. It&apos;s up to you to choose anyway.&lt;/p&gt;
&lt;p&gt;Consequently, each partitions has not to be mapped to 1 host only anymore, but to N hosts. Each node will therefore store this number of partitions:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;number of partition stored on one node = number of replicas × total number of partitions ÷ number of node
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Examples:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We split the ring in 210 = 1024 partitions. We have 3 nodes. We want 3 replicas of data.&lt;br /&gt;
→ Each node will store a copy of the full partition space: &lt;code&gt;3 × 210 ÷ 3 = 210 = 1024 partitions&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;We split the ring in 211 = 2048 partitions. We have 5 nodes. We want 3 replicas of data.&lt;br /&gt;
→ Each node will store &lt;code&gt;211 × 3 ÷ 5 ≈ 1129 partitions&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;We split the ring in 211 = 2048 partitions. We have 6 nodes. We want 3 replicas of data.&lt;br /&gt;
→ Each node will store &lt;code&gt;211 × 3 ÷ 6 = 1024 partitions&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Three rings to rule them all&lt;/h3&gt;
&lt;p&gt;In Swift, there is 3 categories of thing to store: &lt;em&gt;account&lt;/em&gt;, &lt;em&gt;container&lt;/em&gt; and &lt;em&gt;objects&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;An &lt;strong&gt;account&lt;/strong&gt; is what you&apos;d expect it to be, a user account. An account contains &lt;strong&gt;containers&lt;/strong&gt; (the equivalent of Amazon S3&apos;s buckets). Each container can contains user-defined key and values (just like a hash table or a dictionary): values are what Swift call &lt;strong&gt;objects&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Swift wants you to build 3 different and independent rings to store its 3 kind of things (&lt;em&gt;accounts&lt;/em&gt;, &lt;em&gt;containers&lt;/em&gt; and &lt;em&gt;objects&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;Internally, the two first categories are stored as &lt;a href=&quot;http://www.sqlite.org/&quot;&gt;SQLite&lt;/a&gt; databases, whereas the last one is stored using regular files.&lt;/p&gt;
&lt;p&gt;Note that this 3 rings can be stored and managed on 3 completely different set of servers.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/openstack-swift-storage-1.png&quot; alt=&quot;openstack-swift-storage-1&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Data replication&lt;/h2&gt;
&lt;p&gt;Now that we have our storage theory in place (accounts, containers and objects distributed into partitions, themselves stored into multiple zones), let&apos;s go the replication practice.&lt;/p&gt;
&lt;p&gt;When you put something in one of the 3 rings (being an account, a container or an object) it is uploaded into all the zones responsible for the ring partition the object belongs to. This upload into the different zones is the responsibility of the &lt;em&gt;swift-proxy&lt;/em&gt; daemon.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/openstack-swift-replication.png&quot; alt=&quot;openstack-swift-replication&quot; /&gt;&lt;/p&gt;
&lt;p&gt;But if one of the zone is failing, you can&apos;t upload all your copies in all zones at the upload time. So you need a mechanism to be sure the failing zone will catch up to a correct state at some point.&lt;/p&gt;
&lt;p&gt;That&apos;s the role of the &lt;em&gt;swift-{container,account,object}-replicator&lt;/em&gt; processes. This processes are &lt;strong&gt;running on each node part of a zone&lt;/strong&gt; and replicates their contents to nodes of the other zones.&lt;/p&gt;
&lt;p&gt;When they run, they walk through all the contents from all the partitions on the whole file system and for each partition, issue a special &lt;em&gt;REPLICATE&lt;/em&gt; HTTP request to all the other zones responsible for that same partition. The other zone responds with information about the local state of the partition. That allows the replicator process to decide if the remote zone has an up-to-date version of the partition.&lt;/p&gt;
&lt;p&gt;In case of account and containers, it doesn&apos;t check at the partition level, but check each account/container contained inside each partition.&lt;/p&gt;
&lt;p&gt;If something is not up-to-date, it will be pushed using &lt;em&gt;rsync&lt;/em&gt; by the replicator process. This is why you&apos;ll read that the replication updates are &lt;em&gt;&quot;push based&quot;&lt;/em&gt; in Swift documentation.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## Pseudo code describing replication process for accounts
## The principle is exactly the same for containers
for account in accounts:
    # Determine the partition used to store this account
    partition = hash(account) % number_of_partitions
    # The number of zone is the number of replicas configured
    for zone in partition.get_zones_storing_this_partition():
        # Send a HTTP REPLICATE command to the remote swift-account-server process
        version_of_account = zone.send_HTTP_REPLICATE_for(account):
        if version_of_account &amp;lt; account.version()
            account.sync_to(zone)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This replication process is &lt;em&gt;O(number of account × number of replicas)&lt;/em&gt;. The more your number of account will increase and the more you will want replicas for your data, the more the replication time for your accounts will grow. The same rule applies for containers.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## Pseudo code describing replication process for objects
for partition in partitions_storing_objects:
    # The number of zone is the number of replicas configured
    for zone in partition.get_zones_storing_this_partition():
        # Send a HTTP REPLICATE command to the remote swift-object-server process
        verion_of_partition = zone.send_HTTP_REPLICATE_for(partition):
        if version_of_partition &amp;lt; partition.version()
            # Use rsync to synchronize the whole partition
            # and all its objects
            partition.rsync_to(zone)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This replication process is &lt;em&gt;O(number of objects partitions × number of replicas)&lt;/em&gt;. The more your number of objects partitions will increase, and the more you will want replicas for your data, the more the replication time for your objects will grow.&lt;/p&gt;
&lt;p&gt;I think this is something important to know when deciding how to build your Swift architecture. Choose the right number the number of replicas, partitions and nodes.&lt;/p&gt;
&lt;h2&gt;Replication process bottlenecks&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/copy-cat.jpg&quot; alt=&quot;copy-cat&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;File accesses&lt;/h3&gt;
&lt;p&gt;The problem, as you might have guessed, is that to replicate, &lt;strong&gt;it walks through every damn things&lt;/strong&gt;, things being accounts, containers, or object&apos;s partition hash files. This means it need to open and read (part of) a every file your node stores to check that data need or not to be replicated!&lt;/p&gt;
&lt;p&gt;For accounts &amp;amp; containers replication, this is done every 30 seconds by default, but it will likely take more than 30 seconds as soon as you hit around 12 000 containers on a node (see measurements below). Therefore you&apos;ll end up checking consistency of accounts &amp;amp; containers on each all node &lt;strong&gt;all the time&lt;/strong&gt;, using obviously a lot of CPU time.&lt;/p&gt;
&lt;p&gt;For reference, &lt;a href=&quot;http://web.archive.org/web/20120903043209/http://alexyang.sinaapp.com/?p=115&quot;&gt;Alex Yang also did an analysis&lt;/a&gt; of that same problem.&lt;/p&gt;
&lt;h3&gt;TCP connections&lt;/h3&gt;
&lt;p&gt;Worst, the HTTP connections used to send the &lt;em&gt;REPLICATE&lt;/em&gt; commands are not pooled: a new TCP connection is established each time something has to be checked against the same thing stored on a remote zone.&lt;/p&gt;
&lt;p&gt;This is why you&apos;ll see in the &lt;a href=&quot;http://swift.openstack.org/deployment_guide.html&quot;&gt;Swift&apos;s Deployment Guide&lt;/a&gt; this lines listed&lt;br /&gt;
under &lt;a href=&quot;http://swift.openstack.org/deployment_guide.html#general-system-tuning&quot;&gt;&quot;general system tuning&quot;&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## disable TIME_WAIT.. wait..
net.ipv4.tcp_tw_recycle=1
net.ipv4.tcp_tw_reuse=1

## double amount of allowed conntrack
net.ipv4.netfilter.ip_conntrack_max = 262144
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In my humble opinion, this is more an ugly hack than a tuning. If you don&apos;t activate this and if you have a lot of containers on your node, you&apos;ll end up soon with thousands of connections in &lt;em&gt;TIME_WAIT&lt;/em&gt; state, and you indeed risk to overload the IP conntrack module.&lt;/p&gt;
&lt;h3&gt;Container deletion&lt;/h3&gt;
&lt;p&gt;We also should talk about container deletion. When a user deletes a container from its account, the container is &lt;strong&gt;marked as deleted&lt;/strong&gt;. And that&apos;s it. It&apos;s not deleted. Therefore the SQLite database file representing the container will continue to be checked for synchronization, over and over.&lt;/p&gt;
&lt;p&gt;The only way to have a container permanently deleted is to &lt;strong&gt;mark an account as deleted&lt;/strong&gt;. This way the &lt;em&gt;swift-account-reaper&lt;/em&gt; will delete all its containers and, finally, the account.&lt;/p&gt;
&lt;h2&gt;Measurement&lt;/h2&gt;
&lt;p&gt;On a pretty big server, I measured the replications to be done at a speed of around 350 {account,container,object-partitions}/second, which can be a real problem if you choose to build a lots of partition and you have a low &lt;em&gt;number_of_node ⁄ number_of_replicas&lt;/em&gt; ratio.&lt;/p&gt;
&lt;p&gt;For example, the default parameters runs the container replication every 30 seconds. To check replication status of 12 000 containers stored on one node at the speed of 350 containers/seconds, you&apos;ll need around 34 seconds to do so. In the end, you&apos;ll never stop checking replication of your containers, and the more you&apos;ll have containers, the more your &lt;strong&gt;inconsistency window will increase&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Until some of the code is fixed (the HTTP connection pooling probably being the &quot;easiest&quot; one), I warmly recommend to choose correctly the different Swift parameters for your setup. The replication process optimization consists in having the minimum amount of partitions per node, which can be done by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;decreasing the number of partitions&lt;/li&gt;
&lt;li&gt;decreasing the number of replicas&lt;/li&gt;
&lt;li&gt;increasing the number of node&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For very large setups, some code to speed up accounts and containers synchronization, and remove deleted containers will be required, but this does not exist yet, as far as I know.&lt;/p&gt;
</content:encoded></item><item><title>First release of PyMuninCli</title><link>https://julien.danjou.info/blog/pymunincli-0-1/</link><guid isPermaLink="true">https://julien.danjou.info/blog/pymunincli-0-1/</guid><description>Today I release a Python client library to query Munin servers.</description><pubDate>Tue, 17 Apr 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Today I release a &lt;a href=&quot;http://python.org&quot;&gt;Python&lt;/a&gt; client library to query &lt;a href=&quot;http://munin-monitoring.org/&quot;&gt;Munin&lt;/a&gt; servers.&lt;/p&gt;
&lt;p&gt;I wrote it as part of some experiments I did a few weeks ago. I discovered there was no client library to query a Munin server. There&apos;s &lt;a href=&quot;http://aouyar.github.com/PyMunin/&quot;&gt;PyMunin&lt;/a&gt; or &lt;a href=&quot;http://samuelks.com/python-munin/&quot;&gt;python-munin&lt;/a&gt; which help developing Munin plugins, but nothing to access the &lt;em&gt;munin-node&lt;/em&gt; and retrieve its data.&lt;/p&gt;
&lt;p&gt;So I decided to write a quick and simple one, and it&apos;s released under the name of &lt;a href=&quot;https://github.com/jd/pymunincli&quot;&gt;PyMuninCli&lt;/a&gt;, providing the &lt;em&gt;munin.client&lt;/em&gt; Python module.&lt;/p&gt;
</content:encoded></item><item><title>mod_defensible 1.5 released</title><link>https://julien.danjou.info/blog/mod_defensible-1-5/</link><guid isPermaLink="true">https://julien.danjou.info/blog/mod_defensible-1-5/</guid><description>Apache 2.4 being out, I noticed that my good old mod defensible did not compile anymore.</description><pubDate>Tue, 03 Apr 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Apache 2.4 being out, I noticed that my good old &lt;a href=&quot;http://github.com/jd/mod_defensible&quot;&gt;mod_defensible&lt;/a&gt; did not compile anymore.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;http://httpd.apache.org/docs/2.4/developer/new_api_2_4.html&quot;&gt;changes in the new Apache 2.4 API&lt;/a&gt; were small for its concern, so it was pretty easy to update this software to make it compile again.&lt;/p&gt;
&lt;p&gt;Honestly, I&apos;m not sure that this module is really used into the wild, but I still think that it can serve as a good prototype for doing other things so I like keeping it around. :-)&lt;/p&gt;
&lt;p&gt;All this has been triggered by the Apache 2.4 arrival into Debian experimental. Therefore I&apos;ve updated the mod_defensible package to use the new dh_apache2, and imported it into Git at the same time.&lt;/p&gt;
</content:encoded></item><item><title>xpyb 1.3 released</title><link>https://julien.danjou.info/blog/xpyb-1-3/</link><guid isPermaLink="true">https://julien.danjou.info/blog/xpyb-1-3/</guid><description>It took a while to get it out, but finally, 3 years after the latest release (1.2), the version of 1.3 of xpyb (the XCB Python bindngs) is out.</description><pubDate>Thu, 22 Mar 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It took a while to get it out, but finally, 3 years after the latest release (1.2), the version of 1.3 of &lt;a href=&quot;http://cgit.freedesktop.org/xcb/xpyb/&quot;&gt;xpyb&lt;/a&gt; (the &lt;a href=&quot;http://xcb.freedesktop.org&quot;&gt;XCB&lt;/a&gt; Python bindngs) is out.&lt;/p&gt;
&lt;p&gt;This version has a lot of improvement, and major bug fixes (memory corruption and memory leak were tracked down and fixed).&lt;/p&gt;
&lt;p&gt;One amazing feature that is now shipped with that release, is &lt;a href=&quot;https://julien.danjou.info/blog/python-cairo-and-xcb-support&quot;&gt;my code to export the xpyb API to other Python modules&lt;/a&gt;, allowing to draw with &lt;a href=&quot;http://www.cairographics.org/pycairo/&quot;&gt;Pycairo&lt;/a&gt; in Python using XCB.&lt;/p&gt;
&lt;p&gt;Here is an example of a Python program that draws a spiral in a window using xpyb and Pycairo. You need xpyb &amp;gt;= 1.3 and Pycairo &amp;gt;= 1.10 to make this works.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import cairo
import xcb
from xcb.xproto import *

WIDTH, HEIGHT = 600, 600

def draw_spiral(ctx, width, height):
    &quot;&quot;&quot;Draw a spiral with lines!&quot;&quot;&quot;
    wd = .02 * width
    hd = .02 * height

    width -= 2
    height -= 2

    ctx.move_to (width + 1, 1-hd)
    for i in range(9):
        ctx.rel_line_to (0, height - hd * (2 * i - 1))
        ctx.rel_line_to (- (width - wd * (2 *i)), 0)
        ctx.rel_line_to (0, - (height - hd * (2*i)))
        ctx.rel_line_to (width - wd * (2 * i + 1), 0)

    ctx.set_source_rgb (0, 0, 1)
    ctx.stroke()

## Connect to the X server
conn = xcb.connect()
## Get the X server setup
setup = conn.get_setup()
## Generate X ID for our X &quot;objects&quot;
window = conn.generate_id()
pixmap = conn.generate_id()
gc = conn.generate_id()
## Create a new window
conn.core.CreateWindow(setup.roots[0].root_depth, window,
                       # Parent is the root window
                       setup.roots[0].root,
                       0, 0, WIDTH, HEIGHT, 0, WindowClass.InputOutput,
                       setup.roots[0].root_visual,
                       CW.BackPixel | CW.EventMask,
                       [ setup.roots[0].white_pixel, EventMask.ButtonPress | EventMask.EnterWindow | EventMask.LeaveWindow | EventMask.Exposure ])

## Create a pixmap: it will be used to draw with cairo
conn.core.CreatePixmap(setup.roots[0].root_depth, pixmap, setup.roots[0].root,
                       WIDTH, HEIGHT)

## We just need a GC to copy later the pixmap on the window, so create one
## very simple
conn.core.CreateGC(gc, setup.roots[0].root, GC.Foreground | GC.Background,
                   [ setup.roots[0].black_pixel, setup.roots[0].white_pixel ])

## Create a cairo surface
surface = cairo.XCBSurface (conn, pixmap,
                            setup.roots[0].allowed_depths[0].visuals[0], WIDTH, HEIGHT)
## Create a cairo context with that surface
ctx = cairo.Context(surface)

## Paint everything in white
ctx.set_source_rgb (1, 1, 1)
ctx.set_operator (cairo.OPERATOR_SOURCE)
ctx.paint()

## Draw our spiral
draw_spiral (ctx, WIDTH, HEIGHT)

## Map the window on the screen so it gets visible
conn.core.MapWindow(window)

## Flush all X requests to the X server
conn.flush()

while True:
    try:
        event = conn.wait_for_event()
    except xcb.ProtocolException, error:
        print &quot;Protocol error %s received!&quot; % error.__class__.__name__
        break
    except:
        break

    # ExposeEvent are received when we need to refresh the content of the
    # window, so we copy the content of the pixmap (where cairo drew) in the
    # window
    if isinstance(event, ExposeEvent):
        conn.core.CopyArea(pixmap, window, gc, 0, 0, 0, 0, WIDTH, HEIGHT)
    # You click, I quit.
    elif isinstance(event, ButtonPressEvent):
        break
    conn.flush()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Seeing the complexity it is to draw something simple with this technology, I somehow understand why nobody bothered to release or use the code during the last 3 years.&lt;/p&gt;
&lt;p&gt;But hey, now that it&apos;s out, you can build the next Python based desktop environment with bleeding edge technologies. :-)&lt;/p&gt;
</content:encoded></item><item><title>Ten years as a Debian developer</title><link>https://julien.danjou.info/blog/ten-years-as-a-debian-developer/</link><guid isPermaLink="true">https://julien.danjou.info/blog/ten-years-as-a-debian-developer/</guid><description>Ten years ago, I joined the Debian project as a developer.</description><pubDate>Fri, 24 Feb 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Ten years ago, I joined the &lt;a href=&quot;http://www.debian.org&quot;&gt;Debian&lt;/a&gt; project as a developer.&lt;/p&gt;
&lt;p&gt;At that time, I was 18 and in my first year at university, hanging out with the &lt;a href=&quot;http://tuxfamily.org&quot;&gt;TuxFamily&lt;/a&gt; system administrators, which included 3 french Debian developers (sjg, igenibel and creis).&lt;/p&gt;
&lt;p&gt;I was learning Debian packaging while working on &lt;a href=&quot;http://vhffs.org&quot;&gt;VHFFS&lt;/a&gt;, and decided to package one or two non-yet-packaged software for Debian. My friends pushed me into the &lt;a href=&quot;http://nm.debian.org&quot;&gt;NM process&lt;/a&gt;, and &lt;a href=&quot;https://nm.debian.org/nmstatus.php?email=acid@hno3.org&quot;&gt;less than 2 months later&lt;/a&gt; I was a Debian developer. One have to admit that back in the days, the NM process was really fast if you were able to reply to the questions quickly. :-) I think I became the youngest developer among Debian&apos;s ones.&lt;/p&gt;
&lt;p&gt;That was my first steps in a Free Software project, and it was really exciting.&lt;/p&gt;
&lt;p&gt;In 10 years, I&apos;ve been doing a lot of different things for Debian. Sure, I&apos;ve been using it all the years long, but let&apos;s recap a bit what I did, from what I recall.&lt;/p&gt;
&lt;p&gt;My first Debian only project was &lt;a href=&quot;http://packages.debian.org/apt-build&quot;&gt;apt-build&lt;/a&gt; around 2003, and later &lt;a href=&quot;http://packages.debian.org/rebuildd&quot;&gt;rebuildd&lt;/a&gt; in 2007.&lt;/p&gt;
&lt;p&gt;I built the &lt;a href=&quot;https://alioth.debian.org/projects/pkg-xen/&quot;&gt;Xen packaging team&lt;/a&gt; in 2005, I&apos;ve been a Stable Release Manager for a year in 2006, and did heavy bug squashing to release Etch that same year.&lt;/p&gt;
&lt;p&gt;I also was an &lt;a href=&quot;https://nm.debian.org/whoisam.php&quot;&gt;Application Manager in 2006&lt;/a&gt; and managed the application of 2&lt;br /&gt;
Debian developers (&lt;a href=&quot;https://nm.debian.org/nmstatus.php?email=joseparrella%40cantv.net&quot;&gt;Jose Parrella&lt;/a&gt; and &lt;a href=&quot;https://nm.debian.org/nmstatus.php?email=debian%40damianv.com.ar&quot;&gt;Damián Viano&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;I admit I&apos;ve been less active in Debian after 2007, mainly because I was busy working on &lt;a href=&quot;http://awesome.naquadah.org&quot;&gt;awesome&lt;/a&gt;, &lt;a href=&quot;http://www.gnu.org/software/emacs/&quot;&gt;GNU Emacs&lt;/a&gt; and others software.&lt;/p&gt;
&lt;p&gt;Since 2011, I joined the &lt;a href=&quot;http://alioth.debian.org/projects/openstack/&quot;&gt;OpenStack packaging team&lt;/a&gt; and I&apos;m working on OpenStack on a (almost) daily basis.&lt;/p&gt;
&lt;p&gt;I don&apos;t know how many packages I touched, managed or updated, but that should be one or two hundreds. I still maintain &lt;a href=&quot;http://qa.debian.org/developer.php?login=acid&quot;&gt;53 of them&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After all, the adventure has been really pleasant, and I had the chance to work with and meet fabulous and smart people. I always liked this project and what it&apos;s trying to do.&lt;/p&gt;
&lt;p&gt;After all these years, I&apos;m definitively staying! See you in another 10 years, folks! :)&lt;/p&gt;
</content:encoded></item><item><title>Google Calendar notifications using pynotify</title><link>https://julien.danjou.info/blog/google-calendar-pynotify/</link><guid isPermaLink="true">https://julien.danjou.info/blog/google-calendar-pynotify/</guid><description>I use Google Calendar to manage my calendars, and I really missed something to warn me whenever I have an appointment with an alert set.</description><pubDate>Tue, 03 Jan 2012 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I use &lt;a href=&quot;http://google.com/calendar&quot;&gt;Google Calendar&lt;/a&gt; to manage my calendars, and I really missed something to warn me whenever I have an appointment with an alert set.&lt;/p&gt;
&lt;p&gt;So here is an example of a Python program to do such a thing. It is written using the &lt;a href=&quot;http://code.google.com/p/gdata-python-client/&quot;&gt;Google Data APIs Python client library&lt;/a&gt; and pynotify.&lt;/p&gt;
&lt;p&gt;I&apos;ll detail the code here, so you can build your own and adapt it to your needs.&lt;/p&gt;
&lt;p&gt;First, we need to import GTK+ and pynotify, and initialize it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import gtk
import pynotify
pynotify.init(sys.argv[0])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, we need to import gdata Calendar API and connect to the calendar. I&apos;ll use the simple email/password way to login, which is clearly not the best, but it&apos;s also the simplest. Feel free to use OAuth 2.0. :-)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;calendar_service = gdata.calendar.service.CalendarService()
calendar_service.email = &apos;mygooglelogin&apos;
calendar_service.password = &apos;mygooglepassword&apos;
calendar_service.ProgrammaticLogin()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we&apos;re ready to request stuff and notify! First, request the events from the default calendar.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;feed = calendar_service.GetCalendarEventFeed()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can iterate over &lt;em&gt;feed&lt;/em&gt; and do various checks.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for event in feed.entry:
    # If the event status is not confirmed, go to the next event.
    if event.event_status.value != &quot;CONFIRMED&quot;:
        continue
    # Now iterate over all the event dates (usually it has one)
    for when in event.when:
        # Parse start and end time
        try:
            start_time = datetime.datetime.strptime(when.start_time.split(&quot;.&quot;)[0], &quot;%Y-%m-%dT%H:%M:%S&quot;)
            end_time = datetime.datetime.strptime(when.end_time.split(&quot;.&quot;)[0], &quot;%Y-%m-%dT%H:%M:%S&quot;)
        except ValueError:
            # ValueError happens on parsing error. Parsing errors
            # usually happen for &quot;all day&quot; events since they have
            # not time, but we do not care about this events.
            continue
        now = datetime.datetime.now()
        # Check that the event hasn&apos;t already ended
        if end_time &amp;gt; now:
            # Check each alert
            for reminder in when.reminder:
                # We handle only reminders with method &quot;alert&quot;
                # and whose start time minus the reminder delay has passed
                if reminder.method == &quot;alert&quot; \
                        and start_time - datetime.timedelta(0, 60 * int(reminder.minutes)) &amp;lt; now:
                    # Build the notification
                    notification = pynotify.Notification(summary=event.title.text,
                                                         message=event.content.text)
                    # Set an icon from the GTK+ stock icons
                    notification.set_icon_from_pixbuf(gtk.Label().render_icon(gtk.STOCK_DIALOG_INFO,
                                                                              gtk.ICON_SIZE_LARGE_TOOLBAR))
                    notification.set_timeout(0)
                    # Show the notification
                    notification.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running this program, you should see a notification if an appointment has an alert to be raised at that time.&lt;/p&gt;
&lt;p&gt;This should be enough to start to build something.&lt;/p&gt;
&lt;p&gt;If you don&apos;t want to program this into Python, you might want to take a look at &lt;a href=&quot;http://code.google.com/p/gcalcli/wiki/HowTo&quot;&gt;gcalcli&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Using GTK+ stock icons with pynotify</title><link>https://julien.danjou.info/blog/python-notify-with-gtk-stock-icon/</link><guid isPermaLink="true">https://julien.danjou.info/blog/python-notify-with-gtk-stock-icon/</guid><description>It took me a while to find this, so I&apos;m just blogging it so other people will be able to find it.</description><pubDate>Tue, 27 Dec 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It took me a while to find this, so I&apos;m just blogging it so other people will be able to find it.&lt;/p&gt;
&lt;p&gt;I wanted to send a &lt;a href=&quot;http://www.galago-project.org/specs/notification/&quot;&gt;desktop notification&lt;/a&gt; using pynotify, but using a &lt;a href=&quot;http://developer.gnome.org/gtk/2.24/gtk-Stock-Items.html&quot;&gt;GTK+ stock icons&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With the following snippet, I managed to do it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import pynotify
pynotify.init(&quot;myapp&quot;)
import gtk
n = pynotify.Notification(summary=&quot;Summary&quot;, message=&quot;Message!&quot;)
n.set_icon_from_pixbuf(gtk.Label().render_icon(gtk.STOCK_HARDDISK, gtk.ICON_SIZE_LARGE_TOOLBAR))
n.show()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that the use of a &lt;em&gt;Label&lt;/em&gt; is just to have a widget instanciated to use the &lt;em&gt;render_icon()&lt;/em&gt; method. It could be any widget type as far as I understand.&lt;/p&gt;
</content:encoded></item><item><title>My OpenStack work</title><link>https://julien.danjou.info/blog/my-openstack-work/</link><guid isPermaLink="true">https://julien.danjou.info/blog/my-openstack-work/</guid><description>Like I already wrote here last week, I&apos;ve been heavily working on OpenStack for the last weeks.</description><pubDate>Fri, 16 Dec 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Like I already wrote here last week, I&apos;ve been heavily working on &lt;a href=&quot;http://openstack.org&quot;&gt;OpenStack&lt;/a&gt; for the last weeks.&lt;/p&gt;
&lt;p&gt;My first assignment was to package OpenStack for Debian. The packages already present in unstable were mainly done by &lt;a href=&quot;http://thomas.goirand.fr/&quot;&gt;Thomas Goirand&lt;/a&gt;, who based its work on the one done in &lt;a href=&quot;http://ubuntu.com&quot;&gt;Ubuntu&lt;/a&gt;. Therefore, the packages where not in a very good shape for Debian.&lt;/p&gt;
&lt;p&gt;Today Ghe Rivero and I (members of the &lt;a href=&quot;https://alioth.debian.org/projects/openstack&quot;&gt;OpenStack Debian packaging team&lt;/a&gt;) managed to push the &lt;a href=&quot;https://launchpad.net/openstack/+milestone/essex-2&quot;&gt;OpenStack Essex 2 milestone&lt;/a&gt; into unstable with great success. You can now test and deploy OpenStack Essex 2 very easily!&lt;/p&gt;
&lt;p&gt;Packaging OpenStack &lt;a href=&quot;https://review.openstack.org/#dashboard,1669&quot;&gt;made me write several patches&lt;/a&gt;, mainly related to packaging, patches which were all accepted and merged by upstream. This is nice because most of the OpenStack Debian packages lost their &lt;em&gt;debian/patches&lt;/em&gt; directories now!&lt;/p&gt;
&lt;p&gt;Finally, I&apos;ve finished to implement one blueprint I really missed: the &lt;a href=&quot;https://blueprints.launchpad.net/nova/+spec/support-kvm-boot-from-iso&quot;&gt;ability to boot from an ISO image&lt;/a&gt; using &lt;a href=&quot;http://libvirt.org&quot;&gt;libvirt&lt;/a&gt;. The code still needs a review, but it should be included in the Essex 3 milestone if everything&apos;s right.&lt;/p&gt;
</content:encoded></item><item><title>New job, new blog</title><link>https://julien.danjou.info/blog/new-job-new-blog/</link><guid isPermaLink="true">https://julien.danjou.info/blog/new-job-new-blog/</guid><description>It has been a while since I blogged but I&apos;ve been very busy, with my new job and this new blog!  New job! I quitted my job last September, and found another one that I started in October. I&apos;m now the</description><pubDate>Wed, 07 Dec 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It has been a while since I blogged but I&apos;ve been very busy, with my new job and this new blog!&lt;/p&gt;
&lt;h2&gt;New job!&lt;/h2&gt;
&lt;p&gt;I quitted my job last September, and found another one that I started in October. I&apos;m now the lead developer of &lt;a href=&quot;http://www.enovance.com/fr/produits-solutions/opencloud-opensource/enovance-labs&quot;&gt;eNovance Labs&lt;/a&gt;, where I work on the &lt;a href=&quot;http://openstack.org/&quot;&gt;OpenStack&lt;/a&gt; project. So far, this allowed me to contribute heavily to the &lt;a href=&quot;https://alioth.debian.org/projects/openstack&quot;&gt;Debian packaging of OpenStack&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;New blog!&lt;/h2&gt;
&lt;p&gt;In the meantime, I took some time to redesign my personal homepage and this blog, which is now using &lt;a href=&quot;https://github.com/hyde/hyde&quot;&gt;Hyde&lt;/a&gt;, the &lt;a href=&quot;http://python.org&quot;&gt;Python&lt;/a&gt; equivalent of &lt;a href=&quot;http://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt;, which is in &lt;a href=&quot;http://www.ruby-lang.org/&quot;&gt;Ruby&lt;/a&gt;. Since I dislike Ruby (sorry), I preferred to use a Python based generator, and I admit Hyde is really cool.&lt;br /&gt;
Since I really suck at Web design, this one is obviously based on &lt;a href=&quot;http://twitter.github.com/bootstrap/&quot;&gt;Twitter&apos;s bootstrap&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>Google Contacts for Emacs</title><link>https://julien.danjou.info/blog/google-contacts-for-emacs/</link><guid isPermaLink="true">https://julien.danjou.info/blog/google-contacts-for-emacs/</guid><description>I finally finished a thing I was really missing: accessing my Google Contacts from Emacs.</description><pubDate>Mon, 26 Sep 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I finally finished a thing I was really missing: accessing my Google Contacts from Emacs.&lt;/p&gt;
&lt;p&gt;That&apos;s now possible, thanks to my new &lt;a href=&quot;https://github.com/jd/google-contacts.el&quot;&gt;google-contacts.el&lt;/a&gt; package.&lt;/p&gt;
&lt;p&gt;It includes searching for any contact and displaying the result in a window.&lt;br /&gt;
You can also jump to a contact from &lt;a href=&quot;http://gnus.org&quot;&gt;Gnus&lt;/a&gt; by pressing a&lt;br /&gt;
key, and complete e-mail addresses while composing a mail.&lt;/p&gt;
</content:encoded></item><item><title>OAuth 2.0 for Emacs</title><link>https://julien.danjou.info/blog/oauth-2-0-for-emacs/</link><guid isPermaLink="true">https://julien.danjou.info/blog/oauth-2-0-for-emacs/</guid><description>This week, I&apos;ve finished my OAuth 2.0 client implementation for GNU Emacs.</description><pubDate>Fri, 23 Sep 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This week, I&apos;ve finished my &lt;a href=&quot;http://oauth.net/2/&quot;&gt;OAuth 2.0&lt;/a&gt; client implementation for &lt;a href=&quot;http://www.gnu.org/software/emacs/&quot;&gt;GNU Emacs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I have &lt;a href=&quot;http://bzr.savannah.gnu.org/lh/emacs/elpa/revision/126?start_revid=126&quot;&gt;imported it&lt;/a&gt; into &lt;a href=&quot;http://elpa.gnu.org/&quot;&gt;GNU ELPA&lt;/a&gt; so Emacs 24 users will be soon able to install it using the new Emacs packaging system.&lt;/p&gt;
&lt;p&gt;OAuth 2.0 can be used to access, among others, &lt;a href=&quot;http://code.google.com/apis/accounts/docs/OAuth2.html&quot;&gt;Google APIs&lt;/a&gt; or the &lt;a href=&quot;http://developers.facebook.com/docs/authentication/&quot;&gt;Facebook Graph API&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Quitting my job</title><link>https://julien.danjou.info/blog/quitting-my-job/</link><guid isPermaLink="true">https://julien.danjou.info/blog/quitting-my-job/</guid><description>After more than 5 years at Easter-eggs as a system engineer, I&apos;ll be leaving my job to join a new adventure.</description><pubDate>Mon, 29 Aug 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;After more than 5 years at &lt;a href=&quot;http://www.easter-eggs.com&quot;&gt;Easter-eggs&lt;/a&gt; as a system engineer, I&apos;ll be leaving my job soon.&lt;/p&gt;
&lt;p&gt;It has been a fabulous adventure, also due to the &quot;cooperative&quot; nature of the company. I&apos;ve enjoyed worked here, with great people. I do wish them luck for the future. Looking at the numerous things I did for the past years, it has been quite productive!&lt;/p&gt;
&lt;p&gt;Therefore, I&apos;ll be looking for a new job in the next weeks, which will probably keep me busy a bit. :-)&lt;/p&gt;
</content:encoded></item><item><title>Python sets comparisons</title><link>https://julien.danjou.info/blog/python-sets-comparisons/</link><guid isPermaLink="true">https://julien.danjou.info/blog/python-sets-comparisons/</guid><description>This week I lost some time playing with Python&apos;s sets.</description><pubDate>Tue, 17 May 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This week I lost some time playing with &lt;a href=&quot;http://python.org&quot;&gt;Python&lt;/a&gt;&apos;s &lt;a href=&quot;http://docs.python.org/library/stdtypes.html#set&quot;&gt;sets&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After digging into Python source code, I finally discovered there is what seems to be little bug. Anyway, it has been &quot;fixed&quot; in Python 3, fortunately. I did not find if it was reported somewhere, but since it&apos;s fixed, it&apos;s not a big deal.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Python 2.7.1+ (default, Apr 20 2011, 10:53:33) 
[GCC 4.5.2] on linux2
Type &quot;help&quot;, &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.
&amp;gt;&amp;gt;&amp;gt; class A(object):
...     def __eq__(self, other):
...             return True
... 
&amp;gt;&amp;gt;&amp;gt; A() == A()
True
&amp;gt;&amp;gt;&amp;gt; [A()] == [A()]
True
&amp;gt;&amp;gt;&amp;gt; set([A()]) == set([A()])
False
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This clearly did not make any sense to me. I&apos;ve then tested under Python 3.2:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Python 3.2.1a0 (default, May  4 2011, 19:59:25) 
[GCC 4.6.1 20110428 (prerelease)] on linux2
Type &quot;help&quot;, &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.
&amp;gt;&amp;gt;&amp;gt; class A(object):
...     def __eq__(self, other):
...             return True
... 
&amp;gt;&amp;gt;&amp;gt; set([A()]) == set([A()])
Traceback (most recent call last):
  File &quot;&amp;lt;stdin&amp;gt;&quot;, line 1, in &amp;lt;module&amp;gt;
TypeError: unhashable type: &apos;A&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At least, raising an error is saner. It actually helped me to understand what I needed to do to have my sets working correctly with Python 2:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Python 2.7.1+ (default, Apr 20 2011, 10:53:33) 
[GCC 4.5.2] on linux2
Type &quot;help&quot;, &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.
&amp;gt;&amp;gt;&amp;gt; class A(object):
...     def __eq__(self, other):
...             return True
...     def __hash__(self):
...             return 123456789
... 
&amp;gt;&amp;gt;&amp;gt; set([A()]) == set([A()])
True
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Why not Lua</title><link>https://julien.danjou.info/blog/why-not-lua/</link><guid isPermaLink="true">https://julien.danjou.info/blog/why-not-lua/</guid><description>Since my latest announcement of the Lua workshop, I received a couple of emails asking why I discourage the use of Lua.</description><pubDate>Tue, 26 Apr 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Since my latest announcement of the &lt;a href=&quot;https://julien.danjou.info/blog/lua-workshop-at-fabelier-tmplab&quot;&gt;Lua workshop&lt;/a&gt;, I received a couple of emails asking why I discourage the use of &lt;a href=&quot;http://lua.org&quot;&gt;Lua&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Actually, I already wrote out many of &lt;a href=&quot;https://julien.danjou.info/blog/rants-about-lua&quot;&gt;the things I dislike about Lua&lt;/a&gt;. I won&apos;t come back on this technical issues here, but since Lua 5.2 is not yet released (it&apos;s still at alpha stage), they are still relevant nowadays.&lt;/p&gt;
&lt;h2&gt;Stack based API is harder&lt;/h2&gt;
&lt;p&gt;The ease of integration of Lua into a C program is one of the point of Lua. They claim it&apos;s very easy to integrate Lua into your C application, because it does not use pointer, nor reference counting, nor anything that requires a minimum amount of skills to be used.&lt;/p&gt;
&lt;p&gt;It uses a virtual stack based approach. You push or pop things on a stack, and refers to them using a relative or absolute index.&lt;/p&gt;
&lt;p&gt;In order to people who never wrote Lua code to understand, here&apos;s a quick example on how this work. The &lt;em&gt;L&lt;/em&gt; pointer is a Lua environment.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* Create a table on the stack: index 1 */
lua_newtable(L);
/* Push a string on the stack: index 2 */
lua_pushstring(L, &quot;hello&quot;);
/* Push a number on the stack: index 3 */
lua_pushnumber(L, 123);
/* Set newtable[&quot;hello&quot;] = 123 */
lua_settable(L, -3);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You first push a table (in Lua, a table is almost equivalent to what you&apos;d call a hash table in other language), then push the key, the value, and do the assignment operation. In the settable, we use -3 as index, meaning the &quot;3rd item on the stack counting from top&quot;. We could also have written &lt;em&gt;lua_settable(L, 1)&lt;/em&gt;, since the table is also the first item on the stack from the bottom.&lt;/p&gt;
&lt;p&gt;So far, so good.&lt;/p&gt;
&lt;p&gt;Problems arise when you do more complicated stuff. My previous example is what you would typically find in a tutorial, but of course, real life is different, and usually more complex. If you cut the things in different parts, it can start to be more complicated.&lt;/p&gt;
&lt;p&gt;Let&apos;s take a look at the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* Create a table on the stack: index 1 */
lua_newtable(L);
/* Push a string on the stack: index 2 */
lua_pushstring(L, &quot;hello&quot;);
/* Push a number on the stack: index 3 */
lua_pushnumbe(L, mycomputingfunction());
/* Set newtable[&quot;hello&quot;] = 123 */
lua_settable(L, -3);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, we do exactly the same thing, but we do not push &lt;em&gt;123&lt;/em&gt; directly: we compute it.&lt;/p&gt;
&lt;p&gt;And here&apos;s the trick: if your computing function is also using the Lua stack, things can become &lt;em&gt;very&lt;/em&gt; messy. As long as your computing function use the stack cleanly by pushing and poping all its item, and returning the stack &lt;strong&gt;in the same state it was before&lt;/strong&gt;, you&apos;re safe. The problem is that in a complex program, you also write bugs. You do not chose to, but you do. And sometimes, you forget to pop one of the item you fetched from a table.&lt;/p&gt;
&lt;p&gt;Imagine that &lt;em&gt;mycomputingfunction&lt;/em&gt; is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int
mycomputingfunctiong(void)
{
  /* Just push the table we want to fetch
     the number from on the stack */
  pushatableonstack(L);
  lua_pushstring(L, &quot;mykey&quot;);
  lua_gettable(L, -2);
  return lua_tonumber(L, -1);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function works perfectly. It pushes a table, then a key (&lt;em&gt;&quot;mykey&quot;&lt;/em&gt;), then fetches mytable[&quot;mykey&quot;] and pops the key (lua_gettable does push value/pop key itself), and then returns the numeric value of the last item (the fetched one) of the stack.&lt;/p&gt;
&lt;p&gt;However, this function has a bug: it does not pop the table! This does not prevent the function to work. It does not raise a segmentation fault. It does not show any problem under &lt;a href=&quot;http://www.gnu.org/software/gdb/&quot;&gt;gdb&lt;/a&gt;. It does not show any leak under &lt;a href=&quot;http://valgrind.org/&quot;&gt;Valgrind&lt;/a&gt;. It does now show any problem under &lt;strong&gt;any standard C debugging tool&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;But when you&apos;ll start using it, your program will start to do weird things, and you&apos;ll have to spend a huge amount of time debugging it manually, dumping the stack content at each step of your program to watch out what&apos;s wrong.&lt;/p&gt;
&lt;p&gt;Another bad thing, that can happen, is some code poping accidentally an item from the stack, or worst, from an empty stack. This does not raise any error on the Lua side, but will break your program in very unfunny way.&lt;/p&gt;
&lt;p&gt;Even if I&apos;ve been very meticulous writing &lt;a href=&quot;http://awesome.naquadah.org&quot;&gt;awesome&lt;/a&gt;, but we hit that problem regularly.&lt;/p&gt;
&lt;p&gt;The easiest workaround is to use &lt;em&gt;lua_settop(L, 0)&lt;/em&gt; to reset the stack to 0 element. Doing this regularly (like after each program event or treatment) can remove left-over items and avoid the never ending stack grow you may experience if your left-over items continue to pile up. Did I tell you I dislike work-around?&lt;/p&gt;
&lt;p&gt;You could also use &lt;em&gt;lua_call()&lt;/em&gt;, which would avoid such an error, but this would require a huge amount of indirection, and would make write more (useless) code.&lt;/p&gt;
&lt;p&gt;This kind of problem does not exists with pointer based API. If you screw things up, the problem will cause a segmentation fault or leak memory, or cause things you can (easily) debug with standard tools like gdb or Valgrind.&lt;/p&gt;
&lt;h2&gt;No reference counting is a pain&lt;/h2&gt;
&lt;p&gt;Userdata objects are variable Lua size objects embedding a C struct you define. It&apos;s the equivalent of an object in object oriented language.&lt;/p&gt;
&lt;p&gt;Lua does not provide any reference counting for the userdata objects. That means you can push this objects on the stack, use them, but they cannot directly reference each others. If you have a &quot;car&quot; userdata and a &quot;wheel&quot; one, the car cannot hold directly a reference to the wheel. This is not possible because userdata are allocated and garbage collected by Lua, and there&apos;s no way to increase the reference counting yourself.&lt;/p&gt;
&lt;p&gt;So the common hack is to store the wheel into a table as a value, and store&lt;br /&gt;
the table index as an integer into the car data structure.&lt;/p&gt;
&lt;p&gt;This obviously makes memory leaks tracking harder, add huge level of reference indirection in usage (still more code), and does not make the whole process less error prone (at least in my opinion).&lt;/p&gt;
&lt;h2&gt;No paradigm makes you lose time&lt;/h2&gt;
&lt;p&gt;Lua is proud to come with no paradigm and to provide metatables. &lt;a href=&quot;https://julien.danjou.info/blog/rants-about-lua&quot;&gt;I already showed 3 years ago that it has big flaws&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To me, this ain&apos;t no good. Lua is not functional, nor it is object oriented.&lt;/p&gt;
&lt;p&gt;Most people, including me, want one of this paradigm, or any else. Plain old imperative is not enough.&lt;/p&gt;
&lt;p&gt;So you&apos;ll start to build more, or to use something like &lt;a href=&quot;http://loop.luaforge.net/&quot;&gt;LOOP&lt;/a&gt;, which implements an object model. You&apos;ll implement your paradigm. I say life is too short to (re)write a paradigm.&lt;/p&gt;
&lt;p&gt;In &lt;a href=&quot;http://awesome.naquadah.org&quot;&gt;awesome&lt;/a&gt; we wanted to have an object oriented approach (this is kind of typical in such a graphical application context), so we tried to build one.&lt;br /&gt;
To me, this started to be a show stopper when I realized that I&apos;ve ended writing Python object model into Lua while developing &lt;em&gt;awesome (which aims to be a window manager, not a language)&lt;/em&gt;. This is one of the reason I stopped hacking on Lua things.&lt;/p&gt;
&lt;p&gt;I liked Python object model and wanted to have it in Lua, and spending time rewriting Python is just not worth it. I probably should have chose Python, not Lua. YMMV.&lt;/p&gt;
&lt;h2&gt;Embedding may not be a good choice&lt;/h2&gt;
&lt;p&gt;This is not Lua related, but I want to mention it. Googling for &quot;&lt;a href=&quot;http://www.google.fr/search?q=embedding+vs+extending&quot;&gt;embedding vs extending&lt;/a&gt;&quot; will probably tell you more about why you should double check that you really need to embed Lua rather than to extend it.&lt;/p&gt;
&lt;h2&gt;Being small is not an excuse&lt;/h2&gt;
&lt;p&gt;One common argument to choose Lua is that it has a small footprint. Yeah, that&apos;s true, but that&apos;s useless. Bummer! When I program, I don&apos;t have any resource usage pressure. People who have such pressure are either paranoids or playing in the world of embedded computers. This is also a no more existing conception since quad core processors equiped phones are coming into the market. I&apos;m rather confident that what we used to call embedded devices are just dead and are now plain computers. But as usual, YMMV.&lt;/p&gt;
&lt;p&gt;So start to forget about it, run in your underpants and yelling &quot;yay we killed that shit!&quot;, and then use real computers stuff. :-)&lt;/p&gt;
&lt;p&gt;Even &lt;a href=&quot;http://shootout.alioth.debian.org/u32/lua.php&quot;&gt;if benchmarks show how Lua is damn fast&lt;/a&gt;, remember what a benchmark proves: that you can do useless things very fast.&lt;/p&gt;
&lt;h2&gt;Too few extension modules&lt;/h2&gt;
&lt;p&gt;This is not directly Lua&apos;s fault, but there&apos;s too few extension modules for Lua. The community is quite small compared to other big languages&apos; ones.&lt;/p&gt;
&lt;h2&gt;So think twice&lt;/h2&gt;
&lt;p&gt;before you choose Lua (or any other language). My recommendations these days would be not to embed, but to extend. If you really have no choice and need to embed a language into your application, &lt;a href=&quot;http://www.gnu.org/s/guile/&quot;&gt;GNU Guile&lt;/a&gt; is probably worth considering, because it&apos;s a Scheme and therefore a functional language :-), and because it can provides also different languages.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://git.savannah.gnu.org/gitweb/?p=guile.git;a=shortlog;h=refs/heads/lua&quot;&gt;Including Lua&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Lua workshop at Fabelier/tmplab</title><link>https://julien.danjou.info/blog/lua-workshop-at-fabelier-tmplab/</link><guid isPermaLink="true">https://julien.danjou.info/blog/lua-workshop-at-fabelier-tmplab/</guid><description>It seems I&apos;ll be at the Lua workshop at Fabelier/tmplab on April 28th 2011, where I&apos;ll try to present and talk about Lua, how to use it, and why you should probably not use it. ;-)</description><pubDate>Thu, 14 Apr 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It seems I&apos;ll be at the &lt;a href=&quot;http://fabelier.org/lua-programming-language-by-julien-danjou/&quot;&gt;Lua workshop at Fabelier/tmplab&lt;/a&gt; on April 28th 2011, where I&apos;ll try to present and talk about &lt;a href=&quot;http://lua.org&quot;&gt;Lua&lt;/a&gt;, how to use it, and why you should probably not use it. ;-)&lt;/p&gt;
</content:encoded></item><item><title>Using advanced filter with mod_authnz_ldap</title><link>https://julien.danjou.info/blog/using-advanced-filter-with-mod_authnz_ldap/</link><guid isPermaLink="true">https://julien.danjou.info/blog/using-advanced-filter-with-mod_authnz_ldap/</guid><description>How to work around mod_authnz_ldap&apos;s limited filtering by using a custom LDAP filter for Apache authentication.</description><pubDate>Mon, 04 Apr 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As you may know, Apache&apos;s &lt;a href=&quot;http://httpd.apache.org/docs/2.2/mod/mod_authnz_ldap.html&quot;&gt;mod_authzn_ldap&lt;/a&gt; allows to authenticate users in Apache HTTP server using an LDAP server. Unfortunately, it has a little implementation flaw.&lt;/p&gt;
&lt;p&gt;The filter used to authenticate the user is built by abusing the &lt;a href=&quot;http://www.ietf.org/rfc/rfc2255.txt&quot;&gt;RFC 2255&lt;/a&gt; which specifies the LDAP URL format. This format has an &quot;attribute&quot; field which is normally used to specify which attributes should be returned. But &lt;em&gt;mod_authzn_ldap&lt;/em&gt; uses this attribute to compare with the username given by the client. That means that you have to have an attribute in your LDAP entries which matches the username, and you have to use it in the &quot;attribute&quot; part of the URL to get things working.&lt;/p&gt;
&lt;p&gt;Therefore, I wrote a patch to add a format string in the LDAP URL in order to user the provided username in the filter, and ignore the attribute part of the URL, which has no use in such a context anyway.&lt;/p&gt;
&lt;p&gt;The bug has been opened in ASF Bugzilla and has number &lt;a href=&quot;https://issues.apache.org/bugzilla/show_bug.cgi?id=51005&quot;&gt;#51005&lt;/a&gt;, with the patch. The patch is backward compatible with the current configuration format, which is not the best choice in theory, but probably the more pragmatic.&lt;/p&gt;
&lt;p&gt;I&apos;ve no clue on the typical delay for patches inclusion in Apache HTTP&lt;br /&gt;
server, so let&apos;s just wait&apos;n see.&lt;/p&gt;
</content:encoded></item><item><title>Org contacts now part of org-contrib</title><link>https://julien.danjou.info/blog/org-contacts-now-part-of-org-contrib/</link><guid isPermaLink="true">https://julien.danjou.info/blog/org-contacts-now-part-of-org-contrib/</guid><description>Thanks to my recent promotion allowing me to commit directly in Org-mode, I&apos;ve moved Org-contacts into the contrib directory of the Orgmode distribution.</description><pubDate>Fri, 18 Mar 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Thanks to my recent promotion allowing me to commit directly in Org-mode, I&apos;ve moved Org-contacts into the contrib directory of the &lt;a href=&quot;http://www.orgmode.org&quot;&gt;Orgmode&lt;/a&gt; distribution.&lt;/p&gt;
</content:encoded></item><item><title>My latest contributions to the Emacs&apos; world</title><link>https://julien.danjou.info/blog/my-latest-contributions-to-the-emacs-world/</link><guid isPermaLink="true">https://julien.danjou.info/blog/my-latest-contributions-to-the-emacs-world/</guid><description>I spend too much time writing Emacs Lisp code these days. Unfortunately, the more I do the more I find new useful tools to improve my work-flow and save time for doing more Lisp. D&apos;oh.  I did not work</description><pubDate>Tue, 01 Mar 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I spend too much time writing Emacs Lisp code these days. Unfortunately, the more I do the more I find new useful tools to improve my work-flow and save time for doing more Lisp. D&apos;oh.&lt;/p&gt;
&lt;p&gt;I did not work on any big thing these last weeks, so I&apos;m thinking it&apos;s a good time to talk about the various code and patches I sent to multiple Emacs packages.&lt;/p&gt;
&lt;h2&gt;el-get&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/dimitri/el-get&quot;&gt;el-get&lt;/a&gt;, a fabulous tool that installs and handles all the external Emacs packages I use. A friendly war started on the &lt;a href=&quot;http://blog.gmane.org/gmane.emacs.el-get.devel&quot;&gt;development list&lt;/a&gt; about autoloads handling. The discussion was overall pointless, since we had a very hard time to communicate our ideas, and we did not understand each others several times.&lt;/p&gt;
&lt;p&gt;In the end, &lt;em&gt;el-get&lt;/em&gt; now supports autoload correctly and do not load automatically all your packages, improving the startup time, and using the Emacs way to do things. Which is always better, obviously.&lt;/p&gt;
&lt;h2&gt;git-commit-mode&lt;/h2&gt;
&lt;p&gt;I&apos;ve started to use &lt;a href=&quot;https://github.com/rafl/git-commit-mode&quot;&gt;git-commit-mode&lt;/a&gt; some times ago. I usually use &lt;em&gt;git-commit&lt;/em&gt; with the &lt;em&gt;-v&lt;/em&gt; option to see what I&apos;m committing. I though it would be useful to color the diff with &lt;em&gt;diff-mode&lt;/em&gt;, so I &lt;a href=&quot;https://github.com/rafl/git-commit-mode/commit/3e2d1047fff31358c39486cd890d1eb87a464404&quot;&gt;wrote a patch&lt;/a&gt; just to do that, which&lt;br /&gt;
was merged today by Florian.&lt;/p&gt;
&lt;h2&gt;magit&lt;/h2&gt;
&lt;p&gt;Some weeks ago, I decided to give a try to &lt;a href=&quot;http://philjackson.github.com/magit/&quot;&gt;magit&lt;/a&gt;, and loved it. I am not always using it, but for basic operations it is very useful. But I really soon found some things I did not like and therefore send patches to enhance it.&lt;/p&gt;
&lt;p&gt;First, I&apos;ve added &lt;a href=&quot;https://github.com/philjackson/magit/commit/0314e7fd1df2b37b3cd1699afdf2dc3b98aee2d1&quot;&gt;a patch to honor status.showUntrackedFiles&lt;/a&gt; which I use in my home directory. In the mean time, I&apos;ve also added &lt;a href=&quot;https://github.com/philjackson/magit/commit/43cd05081b7e60d3f2dcce696f3a07c135f4e306&quot;&gt;a patch to allow adding an arbitrary file&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Yesterday, I sent another &lt;a href=&quot;https://github.com/philjackson/magit/pull/128&quot;&gt;pull request&lt;/a&gt;, not closed for now, which adds the &lt;a href=&quot;https://github.com/jd/magit/commit/73afce9f0220146a55c6c63735ce48561a277632&quot;&gt;possibility to visit files in another window&lt;/a&gt; from a diff file, and &lt;a href=&quot;https://github.com/jd/magit/commit/82d43edb123f493d639ef0835734e58fca1b8c0a&quot;&gt;the support for add-change-log-entry&lt;/a&gt; directly from the displayed diff. Useful for these old projects still using &lt;em&gt;ChangeLog&lt;/em&gt; files but accessible through git (hi Emacs &amp;amp; Gnus!).&lt;/p&gt;
&lt;h2&gt;Gnus&lt;/h2&gt;
&lt;p&gt;Nothing remarkable, but I write a couple of &lt;a href=&quot;http://git.gnus.org/cgit/gnus.git/commit/?id=3ccee76adca8a830cf781e697119b980cd9fcbe1&quot;&gt;fixes&lt;/a&gt; and &lt;a href=&quot;http://git.gnus.org/cgit/gnus.git/commit/?id=01c211faea248b5d9e35f3662670bb8d12b9b137&quot;&gt;enhancements&lt;/a&gt; to the Sieve manage mode, to the &lt;a href=&quot;http://git.gnus.org/cgit/gnus.git/commit/?id=d715adda2809176649227153d9e97564e755efb6&quot;&gt;Gravatar code&lt;/a&gt; and cleaned-up some very very old code. Also added the possibility to&lt;br /&gt;
&lt;a href=&quot;http://git.gnus.org/cgit/gnus.git/commit/?id=2bd6537597f51762a4b04f81c70d8f2be5dcb690&quot;&gt;set list-identifier as a group parameter&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Org-mode&lt;/h2&gt;
&lt;p&gt;I spent most of my time working on my &lt;a href=&quot;http://git.naquadah.org/?p=~jd/org-mode.git;a=shortlog;h=refs/heads/jd/agenda-format&quot;&gt;jd/agenda-format&lt;/a&gt; branch, which is soon to be merged. I&apos;ve also just got developer access to the Org-mode patch work and repository, so I&apos;ll be able to break things even more! ;-)&lt;/p&gt;
&lt;h2&gt;ERC&lt;/h2&gt;
&lt;p&gt;I &lt;a href=&quot;http://git.savannah.gnu.org/cgit/emacs.git/commit/?id=391de97a758c44e5d38e0c8f0bd50fe5eae09d5f&quot;&gt;fixed&lt;/a&gt; &lt;strong&gt;the&lt;/strong&gt; bug that annoyed me for a long time. Now &lt;em&gt;erc-track&lt;/em&gt; does not reset the last channel status on window visibility changes not made by the user.&lt;/p&gt;
</content:encoded></item><item><title>Announcing Org-contacts</title><link>https://julien.danjou.info/blog/announcing-org-contacts/</link><guid isPermaLink="true">https://julien.danjou.info/blog/announcing-org-contacts/</guid><description>When I started to use Emacs, I got hooked by many stuff like Gnus and Org-mode. One thing I quickly started to hate is how the Lisp code can be old and unmaintained.</description><pubDate>Tue, 08 Feb 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When I started to use Emacs, I got hooked by many stuff like &lt;a href=&quot;http://gnus.org&quot;&gt;Gnus&lt;/a&gt; and &lt;a href=&quot;http://orgmode.org&quot;&gt;Org-mode&lt;/a&gt;. One thing I quickly started to hate is how the Lisp code can be old and unmaintained. That especially applies to &lt;a href=&quot;http://bbdb.sourceforge.net&quot;&gt;BBDB&lt;/a&gt;, which has been unmaintained for years, and has very very very old and obsolete code.&lt;/p&gt;
&lt;p&gt;Therefore I&apos;ve decided to develop my own BBDB replacement based on my lovely Org-mode. It&apos;s called &lt;code&gt;org-contacts&lt;/code&gt;, and it allows you to handle your contact like anything you would handle in Org. This way you can manage them the way you want, without any preset fields or any assumptions like BBDB has.&lt;/p&gt;
&lt;p&gt;I had the chance to present it at the Paris OrgCamp a couple of weeks ago,&lt;br /&gt;
and due to the enthusiastic audience I had, I&apos;m now releasing it to the wide&lt;br /&gt;
Internet.&lt;/p&gt;
</content:encoded></item><item><title>Naquadah theme for Emacs</title><link>https://julien.danjou.info/blog/naquadah-theme-for-emacs/</link><guid isPermaLink="true">https://julien.danjou.info/blog/naquadah-theme-for-emacs/</guid><description>I often post Emacs screenshots on this blog, and consequently receive a bunch of request for my Emacs theme. Therefore I decided to publish it.</description><pubDate>Mon, 31 Jan 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I often post Emacs screenshots on this blog, and consequently receive a bunch of request for my Emacs theme. Therefore I decided to &lt;a href=&quot;https://github.com/jd/naquadah-theme&quot;&gt;publish it&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>OrgCamp Paris 2011 review</title><link>https://julien.danjou.info/blog/orgcamp-paris-2011-review/</link><guid isPermaLink="true">https://julien.danjou.info/blog/orgcamp-paris-2011-review/</guid><description>Yesterday afternoon, I was at the first OrgCamp in Paris.</description><pubDate>Sun, 23 Jan 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Yesterday afternoon, I was at the first &lt;a href=&quot;http://www.lifehacking.fr/mediawiki/index.php/OrgModeCampJanvier2011&quot;&gt;OrgCamp in Paris&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It was my first attendance to a &lt;a href=&quot;http://en.wikipedia.org/wiki/BarCamp&quot;&gt;BarCamp&lt;/a&gt;, and I really liked it. It&apos;s basically the first geek event I do not find boring nor useless.&lt;/p&gt;
&lt;p&gt;There was about 18-20 persons participating, which was quite high, since we all initially though we would have been only 5.&lt;/p&gt;
&lt;p&gt;We had several presentations of various features and personal usages of &lt;a href=&quot;http://www.orgmode.org&quot;&gt;Org-mode&lt;/a&gt;. For my part, I&apos;ve quickly presented the agenda, and my &lt;a href=&quot;http://bbdb.sourceforge.net/&quot;&gt;BBDB&lt;/a&gt; replacement named &lt;strong&gt;org-contacts&lt;/strong&gt; (I&apos;ll probably talk about it on this blog in another post later).&lt;/p&gt;
&lt;p&gt;The only downside was that Bastien (the new Org-mode maintainer) was not able to come and join us. On the other side, there were so much to tell for a first time, I did not have so much time to code. I only have been able to &lt;a href=&quot;http://lists.gnu.org/archive/html/emacs-orgmode/2011-01/msg01002.html&quot;&gt;fix one bug&lt;/a&gt; reported during my agenda presentation.&lt;/p&gt;
&lt;p&gt;In the end, the overall atmosphere was very enthusiastic and friendly, which was extremely pleasant. The #org-mode-fr IRC channel has been created on &lt;a href=&quot;http://freenode.net&quot;&gt;Freenode&lt;/a&gt;, following this event. Feel free to join us.&lt;/p&gt;
&lt;p&gt;Since people liked it so badly, it seems there should be another barcamp in the next months. Stay tuned.&lt;/p&gt;
</content:encoded></item><item><title>Code fontification with Gnus and Org-mode</title><link>https://julien.danjou.info/blog/code-fontification-with-gnus-and-orgmode/</link><guid isPermaLink="true">https://julien.danjou.info/blog/code-fontification-with-gnus-and-orgmode/</guid><description>I&apos;ve added code fontification using Org src blocks inside Gnus.</description><pubDate>Thu, 20 Jan 2011 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve added code fontification using &lt;a href=&quot;http://orgmode.org/manual/Working-With-Source-Code.html#Working-With-Source-Code&quot;&gt;Org src blocks&lt;/a&gt; inside &lt;a href=&quot;http://gnus.org&quot;&gt;Gnus&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnus-org-buffer-fontification-1.png&quot; alt=&quot;gnus-org-buffer-fontification-1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This interprets the block as an Org buffer and fontify it accordingly if &lt;code&gt;org-src-fontify-natively&lt;/code&gt; it set to &lt;code&gt;t&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Thanks to &lt;a href=&quot;http://news.gmane.org/find-root.php?message_id=%3c80k4lj78ui.fsf%40mundaneum.com%3e&quot;&gt;Sébastien Vauban for the original idea&lt;/a&gt; and implementation. Now it works out of the box without any customization.&lt;/p&gt;
</content:encoded></item><item><title>Color contrast correction</title><link>https://julien.danjou.info/blog/color-contrast-correction/</link><guid isPermaLink="true">https://julien.danjou.info/blog/color-contrast-correction/</guid><description>I finally took some time to finish my color contrast corrector.  It&apos;s now able to compare two colors and to tell if they are readable when used as foreground and background color for text rendering. I</description><pubDate>Tue, 23 Nov 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I finally took some time to finish my color contrast corrector.&lt;/p&gt;
&lt;p&gt;It&apos;s now able to compare two colors and to tell if they are readable when used as foreground and background color for text rendering. If they are too close, the code corrects both colors so to they&apos;ll become distant enough to be readable.&lt;/p&gt;
&lt;p&gt;To do that, it uses color coordinates in the &lt;a href=&quot;http://en.wikipedia.org/wiki/Lab_color_space&quot;&gt;CIE L_a_b* colorspace&lt;/a&gt;. This allows to determine the luminance difference between 2 colors very easily by comparing the &lt;em&gt;L&lt;/em&gt; component of the coordinates. The default threshold used to determine readability based on luminance difference is 40 (on 100), which seems to give pretty good results so far.&lt;/p&gt;
&lt;p&gt;Then it uses the &lt;a href=&quot;http://en.wikipedia.org/wiki/Color_difference#CIEDE2000&quot;&gt;CIE Delta E 2000&lt;/a&gt; formula to obtain the distance between colors. A distance of 6 is considered to be enough for the colors to be distinctive in our case, but that can be adjusted anyway. That depends on reader&apos;s eyes.&lt;/p&gt;
&lt;p&gt;If both the color and luminance distances are big enough, the color pair is considered readable when used upon each other.&lt;/p&gt;
&lt;p&gt;If these criteria are not satisfied, the code simply tries to correct the color by adjusting the &lt;em&gt;L&lt;/em&gt; (luminance) component of the colors so their difference is 40. Optionally, the background color can be fixed so only the foreground color would be adjusted; this is especially handy when the color background is not provided by any external style, but it the screen one (like the Emacs frame background in my case).&lt;/p&gt;
&lt;p&gt;Here is an example result generated over 10 pairs of random colors. Left colors are randomly generated, and right colors are the corrected one.&lt;/p&gt;
&lt;p&gt;&amp;lt;table style=&quot;border-collapse: collapse; width: 100%; font-family: monospace; font-size: 0.85em;&quot;&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;&amp;lt;th style=&quot;text-align: left; padding: 6px 10px;&quot;&amp;gt;Original&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;&amp;lt;th style=&quot;text-align: left; padding: 6px 10px;&quot;&amp;gt;Corrected&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px; background-color: #698b69; color: #ababab;&quot;&amp;gt;DarkSeaGreen4 / gray67&amp;lt;/td&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px;&quot;&amp;gt;→&amp;lt;/td&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px; background-color: #4a6b4b; color: #cccccc;&quot;&amp;gt;#4a6b4b / #cccccc&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px; background-color: #6c7b8b; color: #228b22;&quot;&amp;gt;SlateGray4 / forest green&amp;lt;/td&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px;&quot;&amp;gt;→&amp;lt;/td&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px; background-color: #9faec0; color: #005700;&quot;&amp;gt;#9faec0 / #005700&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px; background-color: #212121; color: #5c5c5c;&quot;&amp;gt;grey13 / grey36&amp;lt;/td&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px;&quot;&amp;gt;→&amp;lt;/td&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px; background-color: #131313; color: #6c6c6c;&quot;&amp;gt;#131313 / #6c6c6c&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px; background-color: #9f79ee; color: #f0fff0;&quot;&amp;gt;MediumPurple2 / honeydew&amp;lt;/td&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px;&quot;&amp;gt;→&amp;lt;/td&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px; background-color: #9e78ed; color: #f0fff0;&quot;&amp;gt;#9e78ed / #f0fff0&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px; background-color: #6e6e6e; color: #66cd00;&quot;&amp;gt;grey43 / chartreuse3&amp;lt;/td&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px;&quot;&amp;gt;→&amp;lt;/td&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px; background-color: #5e5e5e; color: #79de25;&quot;&amp;gt;#5e5e5e / #79de25&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px; background-color: #faf0e6; color: #ee1289;&quot;&amp;gt;linen / DeepPink2&amp;lt;/td&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px;&quot;&amp;gt;→&amp;lt;/td&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px; background-color: #faf0e6; color: #ee1289;&quot;&amp;gt;linen / DeepPink2&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px; background-color: #53868b; color: #0000ff;&quot;&amp;gt;CadetBlue4 / blue1&amp;lt;/td&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px;&quot;&amp;gt;→&amp;lt;/td&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px; background-color: #6c9fa4; color: #0000e1;&quot;&amp;gt;#6c9fa4 / #0000e1&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px; background-color: #545454; color: #cdb38b;&quot;&amp;gt;gray33 / NavajoWhite3&amp;lt;/td&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px;&quot;&amp;gt;→&amp;lt;/td&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px; background-color: #525252; color: #cfb58c;&quot;&amp;gt;#525252 / #cfb58c&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px; background-color: #7fff00; color: #cd9b9b;&quot;&amp;gt;chartreuse1 / RosyBrown3&amp;lt;/td&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px;&quot;&amp;gt;→&amp;lt;/td&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px; background-color: #9cff38; color: #b28282;&quot;&amp;gt;#9cff38 / #b28282&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px; background-color: #c71585; color: #ff1493;&quot;&amp;gt;medium violet red / DeepPink1&amp;lt;/td&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px;&quot;&amp;gt;→&amp;lt;/td&amp;gt;
&amp;lt;td style=&quot;padding: 6px 10px; background-color: #9c0060; color: #ff55b9;&quot;&amp;gt;#9c0060 / #ff55b9&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;&lt;/p&gt;
&lt;p&gt;All this has been written in Emacs Lisp. The code is now available in &lt;a href=&quot;http://www.gnus.org&quot;&gt;Gnus&lt;/a&gt; (and therefore in Emacs 24) in the packages &lt;a href=&quot;http://git.gnus.org/cgit/gnus.git/tree/lisp/color-lab.el&quot;&gt;color-lab&lt;/a&gt; and &lt;a href=&quot;http://git.gnus.org/cgit/gnus.git/tree/lisp/shr-color.el&quot;&gt;shr-color&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A future work would be to add support for colour blindness.&lt;/p&gt;
&lt;p&gt;As a side note, several people pointed me at the &lt;a href=&quot;http://www.w3.org/TR/WCAG10/&quot;&gt;WCAG&lt;/a&gt; formulas to determine luminance and contrast ratio. These are probably good criteria to choose your color when designing a user interface. However, they are not enough to determine if displayed color will be readable. This means you can use them if you are a designer, but IMHO they are pretty weak for detecting and correcting colors you did not choose.&lt;/p&gt;
</content:encoded></item><item><title>Elisp color manipulation routines</title><link>https://julien.danjou.info/blog/elisp-color-manipulation-routines/</link><guid isPermaLink="true">https://julien.danjou.info/blog/elisp-color-manipulation-routines/</guid><description>Last week, I spent some time implementing various color manipulation routines. The ultimate goal was to find a way to determine if a text in a certain color was readable on a background with a differe</description><pubDate>Sat, 20 Nov 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last week, I spent some time implementing various color manipulation routines. The ultimate goal was to find a way to determine if a text in a certain color was readable on a background with a different color.&lt;/p&gt;
&lt;p&gt;Something I failed to do so far, despite my research in the area.&lt;/p&gt;
&lt;p&gt;However, since I think my code could be useful for other people, I&apos;ve set up a tiny &lt;a href=&quot;http://git.naquadah.org/?p=~jd/color-el.git;a=summary&quot;&gt;git repository&lt;/a&gt; with the routines I wrote.&lt;/p&gt;
&lt;p&gt;The funniest one to implement was &lt;a href=&quot;http://en.wikipedia.org/wiki/Color_difference#CIEDE2000&quot;&gt;CIEDE2000&lt;/a&gt;. I verified my code with the data given in &lt;a href=&quot;http://www.ece.rochester.edu/~gsharma/ciede2000/ciede2000noteCRNA.pdf&quot;&gt;the specifications&lt;/a&gt; and can assure it&apos;s correct. :-)&lt;/p&gt;
</content:encoded></item><item><title>Org-mode and holidays</title><link>https://julien.danjou.info/blog/org-mode-and-holidays/</link><guid isPermaLink="true">https://julien.danjou.info/blog/org-mode-and-holidays/</guid><description>Org-mode has a nice option which allows you to show week-end days in a different color in your agenda.</description><pubDate>Mon, 15 Nov 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;http://orgmode.org&quot;&gt;Org-mode&lt;/a&gt; has a nice option which allows you to show week-end days in a different color in your agenda. That means that Saturday and Sunday (when I do not work) are fontified with &lt;code&gt;org-agenda-date-weekend&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;But there are other days I do not work, like my vacations or holidays.&lt;/p&gt;
&lt;p&gt;Therefore, I&apos;ve wrote a patch to add &lt;code&gt;org-agenda-day-face-function&lt;/code&gt; which is optionally called to determine what should be the face used to fontify a day. &lt;a href=&quot;http://lists.gnu.org/archive/html/emacs-orgmode/2010-11/msg00542.html&quot;&gt;This&lt;/a&gt; allows me to use the same face for holidays and for week-end days, like for last Thursday which was an holiday in France.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/emacs-org-mode-holidays-1.png&quot; alt=&quot;emacs-org-mode-holidays-1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;That patch has been merged in Org last week.&lt;/p&gt;
</content:encoded></item><item><title>Google Maps for Emacs: moving, caching and home</title><link>https://julien.danjou.info/blog/google-maps-for-emacs-moving-caching-home/</link><guid isPermaLink="true">https://julien.danjou.info/blog/google-maps-for-emacs-moving-caching-home/</guid><description>Last week, I worked on my Google Maps for Emacs extension. I&apos;ve introduced a new format handling for locations which include the longitude and latitude.</description><pubDate>Mon, 08 Nov 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last week, I worked on my Google Maps for Emacs extension. I&apos;ve introduced a new format handling for locations which include the longitude and latitude. The initial format was just a string describing the location, which was obviously too limited.&lt;/p&gt;
&lt;p&gt;It now prints coordinates of the different elements when the mouse is over the map, with other information.&lt;/p&gt;
&lt;p&gt;It also center the map on &lt;em&gt;M-x google-maps&lt;/em&gt; and set a default zoom level. This is something which was not set because it&apos;s not a good idea to set center coordinates in order to see all points on the map automatically. But you can still remove the centering by pressing &lt;em&gt;&quot;C&quot;&lt;/em&gt;. On the other hand, setting it automatically allows to move the map easily, and I think that what most users want to do.&lt;/p&gt;
&lt;p&gt;I&apos;ve also added a &quot;place my home on the map&quot; feature, accessible by pressing &lt;code&gt;h&lt;/code&gt; on any map. That adds a marker according to the location set in Emacs using the &lt;code&gt;calendar-&lt;/code&gt; variables.&lt;/p&gt;
&lt;p&gt;This feature is also available under &lt;a href=&quot;http://orgmode.org&quot;&gt;Org&lt;/a&gt; by pressing &lt;em&gt;C-u C-c M-l&lt;/em&gt;, which shows the location of your appointment with your home on the map too.&lt;/p&gt;
&lt;p&gt;Finally, you also get caching so it does not request images you already seen, which makes the moving nicer and faster to use, and prompt history.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/emacs-google-maps-move-1.png&quot; alt=&quot;emacs-google-maps-move-1&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>Icon category support in Org-mode</title><link>https://julien.danjou.info/blog/icon-category-support-in-org-mode/</link><guid isPermaLink="true">https://julien.danjou.info/blog/icon-category-support-in-org-mode/</guid><description>My latest patch for Org mode has been accepted by Carsten today. It adds support for custom category icons in all views, like agenda or todo.</description><pubDate>Thu, 04 Nov 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;My latest patch for &lt;a href=&quot;http://orgmode.org&quot;&gt;Org mode&lt;/a&gt; has been accepted by Carsten today. It adds support for custom category icons in all views, like agenda or todo.&lt;/p&gt;
&lt;p&gt;You just need to configure &lt;em&gt;org-agenda-category-icon-alist&lt;/em&gt; and it will work out of the box.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/emacs-org-category-icons-1.png&quot; alt=&quot;emacs-org-category-icons-1&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>Transparent GIF support in Emacs 24</title><link>https://julien.danjou.info/blog/transparent-gif-support-in-emacs24/</link><guid isPermaLink="true">https://julien.danjou.info/blog/transparent-gif-support-in-emacs24/</guid><description>Last week, I wrote a couple of patches to add support for transparency when Emacs is displaying GIF images.</description><pubDate>Tue, 02 Nov 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last week, I wrote a &lt;a href=&quot;http://lists.gnu.org/archive/html/emacs-devel/2010-10/msg01009.html&quot;&gt;couple of patches&lt;/a&gt; to add support for transparency when Emacs is displaying &lt;a href=&quot;http://en.wikipedia.org/wiki/Graphics_Interchange_Format&quot;&gt;GIF&lt;/a&gt; images.&lt;/p&gt;
&lt;p&gt;Until now, it was displaying the color used to define transparency in the file data. Now it displays the image correctly by using the frame color as the transparency color, like it&apos;s done for other image formats.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/emacs-gif-transparent-1.png&quot; alt=&quot;emacs-gif-transparent-1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The patches have not been merged yet, but will probably be soon.&lt;/p&gt;
</content:encoded></item><item><title>No more dashes in Emacs 24 mode-line</title><link>https://julien.danjou.info/blog/no-more-dashes-in-emacs24-mode-line/</link><guid isPermaLink="true">https://julien.danjou.info/blog/no-more-dashes-in-emacs24-mode-line/</guid><description>We all know the good old Emacs mode-line you got under every window. Since the beginning (a long time ago), it starts and ends with dashes. I&apos;ve proposed a patch to remove them.</description><pubDate>Wed, 20 Oct 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;We all know the good old Emacs mode-line you got under every window. Since the beginning (a long time ago), it starts and ends with dashes. I&apos;ve proposed &lt;a href=&quot;http://lists.gnu.org/archive/html/emacs-devel/2010-10/msg00675.html&quot;&gt;a patch&lt;/a&gt; to remove them.&lt;/p&gt;
&lt;p&gt;Before:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/emacs-dashes.png&quot; alt=&quot;Screenshot of Emacs with dashes in the mode line&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/emacs-no-dashes-2.png&quot; alt=&quot;Screenshot of Emacs without dashes in the mode line&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This has been merged in Emacs 24. You won&apos;t see any more ugly dashes in graphical mode.&lt;/p&gt;
</content:encoded></item><item><title>Enhancing Emacs mouse avoidance</title><link>https://julien.danjou.info/blog/enhancing-emacs-mouse-avoidance/</link><guid isPermaLink="true">https://julien.danjou.info/blog/enhancing-emacs-mouse-avoidance/</guid><description>Enhancing Emacs mouse-avoidance-mode to respect the invisible mouse pointer, merged in Emacs 24.</description><pubDate>Tue, 19 Oct 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recent &lt;em&gt;Emacs&lt;/em&gt; versions have a wonderful capacity to hide the mouse pointer as soon as you type and insert characters in a buffer. This is controlled by the &lt;code&gt;make-pointer-invisible&lt;/code&gt; variable, which is set to t by default.&lt;/p&gt;
&lt;p&gt;However that does not hide the pointer when simply moving the cursor on screen. Therefore, I&apos;ve started to use &lt;code&gt;mouse-avoidance-mode&lt;/code&gt;, which make the mouse pointer jump if your cursor hits it.&lt;/p&gt;
&lt;p&gt;Unfortunately, if your cursor hits the invisible mouse pointer, &lt;code&gt;mouse-avoidance-mode&lt;/code&gt; makes it jump too, because it does not know it is invisible.&lt;/p&gt;
&lt;p&gt;Well, it &lt;em&gt;did&lt;/em&gt; not know. Now it does, &lt;a href=&quot;http://lists.gnu.org/archive/html/emacs-devel/2010-10/msg00574.html&quot;&gt;thanks to my patches&lt;/a&gt; which have been merged in Emacs 24. Using the new function &lt;code&gt;frame-pointer-invisible-p&lt;/code&gt;, one can know if the mouse pointer has been hidden by Emacs. Therefore I enhanced `mouse-avoidance-mode&apos; to use it, and everything is alright now. :-)&lt;/p&gt;
</content:encoded></item><item><title>Why notmuch is not much good</title><link>https://julien.danjou.info/blog/why-notmuch-is-not-much-good/</link><guid isPermaLink="true">https://julien.danjou.info/blog/why-notmuch-is-not-much-good/</guid><description>I&apos;ve recently got a mail from one of my faithful reader, asking why not considering notmuch.</description><pubDate>Thu, 07 Oct 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve recently got a mail from one of my faithful reader, asking why not considering &lt;a href=&quot;http://notmuchmail.org/&quot;&gt;notmuch&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Actually, I think notmuch already exists in a better way, and that&apos;s called&lt;br /&gt;
&lt;a href=&quot;http://en.wikipedia.org/wiki/Internet_Message_Access_Protocol&quot;&gt;IMAP&lt;/a&gt; and &lt;a href=&quot;http://en.wikipedia.org/wiki/Sieve_(mail_filtering_language)&quot;&gt;Sieve&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;What &lt;em&gt;notmuch&lt;/em&gt; does, is tagging your mail with tags (obviously), based on filtering rules you write. The big downside is that you have to tag all your mails on your computer.&lt;/p&gt;
&lt;p&gt;And if you use several computers, you&apos;ll have to tag several times your mails. And you&apos;ll have to find a way to maintain your rules to be identical on all your computers. That does not scale.&lt;/p&gt;
&lt;p&gt;Using Sieve for mail filtering, one can do already that actually, and much&lt;br /&gt;
more.&lt;/p&gt;
&lt;p&gt;A &lt;em&gt;notmuch&lt;/em&gt; rule like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;notmuch tag +intel from:intel.com and not tag:intel
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Can be written as a Sieve rule like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if address :all :contains &quot;From&quot; &quot;intel.com&quot; {
	addflag &quot;intel&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The flags extension for Sieve is explained in &lt;a href=&quot;http://tools.ietf.org/html/rfc5232&quot;&gt;RFC5232&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The Sieve based solution has the advantage of being treated server side, and therefore not subject to multiple or different MUA usages. It&apos;s also fast, if you use a good IMAP server like &lt;a href=&quot;http://www.dovecot.org&quot;&gt;Dovecot&lt;/a&gt;, which has indexing, etc.&lt;/p&gt;
&lt;p&gt;Furthermore, Sieve can obviously do a lot more than tagging, like splitting into different mailboxes, filtering with regexp usage, vacation, etc.&lt;/p&gt;
&lt;p&gt;And if you want to fetch your mail locally, you can synchronize the IMAP box entirely with any software able to (like &lt;a href=&quot;http://github.com/jgoerzen/offlineimap/wiki&quot;&gt;OfflineIMAP&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Now, what&apos;s probably missing, is a correct support for IMAP flags on various MUA around. But that&apos;s not something &lt;em&gt;notmuch&lt;/em&gt; helps to solve either. :-)&lt;/p&gt;
</content:encoded></item><item><title>Gnus and Gravatar support</title><link>https://julien.danjou.info/blog/gnus-gravatar-support/</link><guid isPermaLink="true">https://julien.danjou.info/blog/gnus-gravatar-support/</guid><description>This last couple of days I&apos;ve been dedicated making Gnus… fresher.</description><pubDate>Sat, 25 Sep 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This last couple of days I&apos;ve been dedicated making &lt;a href=&quot;http://www.gnus.org&quot;&gt;Gnus&lt;/a&gt;… fresher.&lt;/p&gt;
&lt;p&gt;I&apos;ve decided to give a whirl on &lt;a href=&quot;http://www.gravatar.com&quot;&gt;Gravatar&lt;/a&gt; support. I already tried the &lt;code&gt;gravatar.el&lt;/code&gt; lying on the Interweb, but well, it was crap: it used &lt;code&gt;wget&lt;/code&gt; to fetch pictures, therefore was totally synchronous. Reading each mail was slower. The cache did not even have TTL, as far as I recall.&lt;/p&gt;
&lt;p&gt;So, I&apos;ve now wrote &lt;a href=&quot;http://git.gnus.org/cgit/gnus.git/tree/lisp/gravatar.el&quot;&gt;&lt;code&gt;gravatar.el&lt;/code&gt;&lt;/a&gt; implementing the Gravatar API. Asynchronously of course. With cache, TTL, etc. Perfect. :-)&lt;/p&gt;
&lt;p&gt;Then I&apos;ve composed &lt;a href=&quot;http://git.gnus.org/cgit/gnus.git/tree/lisp/gnus-gravatar.el&quot;&gt;&lt;code&gt;gnus-gravatar.el&lt;/code&gt;&lt;/a&gt;, implementing a washing function adding Gravatar for &lt;code&gt;From&lt;/code&gt; field and/or &lt;code&gt;Cc&lt;/code&gt;/&lt;code&gt;To&lt;/code&gt; fields, like done for &lt;a href=&quot;https://www.cs.indiana.edu/picons/&quot;&gt;picons&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As I was expecting, the patch was badly received by the GNU guys, which start talking about thinks like external resources, privacy, non-free software, etc. Boring.&lt;/p&gt;
&lt;p&gt;Fortunately, Lars allowed me to push the patch in git so everybody can give it a try. I&apos;m now waiting for feedbacks in order to know if I will have to maintain this patch outside Gnus, or not.&lt;/p&gt;
&lt;p&gt;Here&apos;s the mandatory screenshot.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/gnus-gravatar-1.png&quot; alt=&quot;gnus-gravatar-1&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>Gnus news is good news!</title><link>https://julien.danjou.info/blog/gnus-news-is-good-news/</link><guid isPermaLink="true">https://julien.danjou.info/blog/gnus-news-is-good-news/</guid><description>As I already wrote too many times, I&apos;ve started to use Gnus 6 months ago, and never looked back.</description><pubDate>Thu, 23 Sep 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As I already wrote too many times, I&apos;ve started to use &lt;a href=&quot;http://gnus.org&quot;&gt;Gnus&lt;/a&gt; 6 months ago, and never looked back.&lt;/p&gt;
&lt;p&gt;At that time, I joined the &lt;a href=&quot;http://gnus.org/resources.html&quot;&gt;ding mailing list&lt;/a&gt; in order to ask some dumb questions and, once, send a patch. There were very low activity on that list.&lt;/p&gt;
&lt;p&gt;Until Lars, the original Gnus author, came back.&lt;/p&gt;
&lt;p&gt;Three weeks ago, he started to wrote a new wash function to render &lt;a href=&quot;http://en.wikipedia.org/wiki/HTML&quot;&gt;HTML&lt;/a&gt; mails properly, with pictures. It&apos;s named &lt;code&gt;gnus-html&lt;/code&gt;, and is (for now) based on &lt;a href=&quot;http://w3m.sourceforge.net&quot;&gt;w3m&lt;/a&gt; (but not on &lt;a href=&quot;http://emacs-w3m.namazu.org/&quot;&gt;emacs-w3m&lt;/a&gt;, which is not part of Emacs).&lt;/p&gt;
&lt;p&gt;Last week, I&apos;ve sent a set of patches to replace the usage of &lt;a href=&quot;http://curl.haxx.se&quot;&gt;curl&lt;/a&gt; by the standard &lt;code&gt;url-retrieve&lt;/code&gt; function to fetch images, plus various enhancement. It seems that my work was good enough that Lars offered me write access to the git repository. I can therefore mess up the Gnus entirely. Hurrah!&lt;/p&gt;
&lt;p&gt;I&apos;ve continued to work on &lt;code&gt;gnus-html&lt;/code&gt; and recently merged a set of patches improving image retrieval (which is now done in parallel) and starting to use &lt;code&gt;url-cache&lt;/code&gt; to cache image for a defined period of time. Of course, I found a bunch of tiny bug and special case while reading RSS feeds and various HTML mails, and fixed them all along.&lt;/p&gt;
&lt;p&gt;Lars added a &lt;a href=&quot;http://xmlsoft.org&quot;&gt;libxml&lt;/a&gt; binding for Emacs 24, providing the &lt;code&gt;html-parse-string&lt;/code&gt; function. His future plan seems to be the abandon of w3m in favor of a native parsing via libxml to render HTML, and therefore, HTML mails.&lt;/p&gt;
&lt;p&gt;I should also mention the new &lt;code&gt;nnimap&lt;/code&gt; back-end; Gnus has been designed to read &lt;a href=&quot;http://en.wikipedia.org/wiki/Network_News_Transfer_Protocol&quot;&gt;NNTP&lt;/a&gt; newsgroups, and not mails. Consequently, it had a very poor behaviour when used with a back-end such has &lt;a href=&quot;http://en.wikipedia.org/wiki/Internet_Message_Access_Protocol&quot;&gt;IMAP&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Lars took a week to rewrite entirely our dear &lt;code&gt;nnimap&lt;/code&gt; back-end, and make it act in a more expected way. There&apos;s still a bunch of bug and code to write, but it is at least usable and seems faster than the old code.&lt;/p&gt;
&lt;p&gt;Last thing I did was to rewrite the icon support in the group buffer. When I started to use Gnus, I was curious and tried to configure this. I never managed to make it work, and now know and understand why it was broken. So I ended rewriting entirely, and now it works. I never though I would understand, fix, and commit this code when reading the Gnus documentation this winter, but hell yeah, I did.&lt;/p&gt;
&lt;p&gt;Now I&apos;ve still several little project to improve things in all sort of area.&lt;br /&gt;
We&apos;ll see what I&apos;ll do next. :-)&lt;/p&gt;
</content:encoded></item><item><title>Emacs, Org, whatever the weather!</title><link>https://julien.danjou.info/blog/emacs-org-whatever/</link><guid isPermaLink="true">https://julien.danjou.info/blog/emacs-org-whatever/</guid><description>Another week, another Emacs extension!</description><pubDate>Wed, 08 Sep 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Another week, another &lt;a href=&quot;http://www.gnu.org/software/emacs/&quot;&gt;Emacs&lt;/a&gt; extension!&lt;/p&gt;
&lt;p&gt;I had (once again) a wonderful idea: what if I could have the weather forecasts in my &lt;a href=&quot;http://orgmode.org&quot;&gt;Org&lt;/a&gt; agenda? Wouldn&apos;t that be wonderful?&lt;/p&gt;
&lt;p&gt;My quest started by looking for a service offering a good weather forecast API. I found nothing simple as the hidden Google Weather API, which is nice, but… not documented. Not at all. Not a single line. Nah.&lt;/p&gt;
&lt;p&gt;Then, I wrote a &lt;strong&gt;google-weather&lt;/strong&gt; extension, implementing a basic Emacs Lisp API to retrieve data from the Google service:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ELISP&amp;gt; (google-weather-data-&amp;gt;forecast (google-weather-get-data &quot;Paris&quot;))
(((9 8 2010)
  (low &quot;53&quot;)
  (high &quot;63&quot;)
  (icon &quot;http://www.google.com/ig/images/weather/rain.gif&quot;)
  (condition &quot;Rain&quot;))
 ((9 9 2010)
  (low &quot;53&quot;)
  (high &quot;69&quot;)
  (icon &quot;http://www.google.com/ig/images/weather/chance_of_rain.gif&quot;)
  (condition &quot;Scattered Showers&quot;))
 ((9 10 2010)
  (low &quot;54&quot;)
  (high &quot;72&quot;)
  (icon &quot;http://www.google.com/ig/images/weather/partly_cloudy.gif&quot;)
  (condition &quot;Partly Cloudy&quot;))
 ((9 11 2010)
  (low &quot;55&quot;)
  (high &quot;75&quot;)
  (icon &quot;http://www.google.com/ig/images/weather/partly_cloudy.gif&quot;)
  (condition &quot;Partly Cloudy&quot;)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;My API even implements data caching, which is nice to speed up the agenda display.&lt;/p&gt;
&lt;p&gt;By the way, I think my next job will be to hack on the &lt;em&gt;url-cache&lt;/em&gt; feature of Emacs, which is utterly buggy and has probably never be used. But that&apos;s another topic.&lt;/p&gt;
&lt;p&gt;Finally, I just had to write another module on top of that to export the forecasts to Org. A screen shot is probably better than a long and boring explanation, so here&apos;s the result.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/org-google-weather-1.png&quot; alt=&quot;org-google-weather-1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;My only regret is that the icons provided by Google are ugly squares, so I did not want to use them. On the other hand, I did not found any icon set that would have all the icons Google provides (around 20). So I felt back on the &lt;a href=&quot;http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html&quot;&gt;icon naming specification&lt;/a&gt; to map the Google images to standard images. Any better idea would be welcome, of course.&lt;/p&gt;
&lt;p&gt;All the information can be found on the &lt;a href=&quot;https://github.com/jd/google-weather.el&quot;&gt;Google Weather for Emacs extension homepage&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Emacs and OfflineIMAP</title><link>https://julien.danjou.info/blog/emacs-and-offlineimap/</link><guid isPermaLink="true">https://julien.danjou.info/blog/emacs-and-offlineimap/</guid><description>I recently decided to use OfflineIMAP to synchronize my mails on my laptop. It&apos;s a great piece of software, and allows me to read my mail while I&apos;m offline.</description><pubDate>Fri, 03 Sep 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I recently decided to use &lt;a href=&quot;http://wiki.github.com/jgoerzen/offlineimap/&quot;&gt;OfflineIMAP&lt;/a&gt; to synchronize my mails on my laptop. It&apos;s a great piece of software, and allows me to read my mail while I&apos;m offline.&lt;/p&gt;
&lt;p&gt;I use it with &lt;a href=&quot;http://www.gnus.org&quot;&gt;Gnus&lt;/a&gt;, of course. But I lacked a proper way to integrate OfflineIMAP with it, so I decided to write a little Emacs extension to run and monitor OfflineIMAP directly from Emacs.&lt;/p&gt;
&lt;p&gt;Here comes &lt;a href=&quot;https://github.com/jd/offlineimap.el&quot;&gt;offlineimap.el&lt;/a&gt;, an Emacs extension to run OfflineIMAP directly within Emacs. It will display OfflineIMAP output in a buffer, and optionally shows the current OfflineIMAP operation in the mode line.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/offlineimap-el-1.png&quot; alt=&quot;offlineimap-el-1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;By default the status is in the mode line only if you are in the Gnus group buffer. But that&apos;s customizable, of course, since this is Emacs!&lt;/p&gt;
&lt;p&gt;If you are using &lt;a href=&quot;http://github.com/dimitri/el-get&quot;&gt;el-get&lt;/a&gt;, there&apos;s already a recipe to install it!&lt;/p&gt;
</content:encoded></item><item><title>Emacs, Google Maps and BBDB</title><link>https://julien.danjou.info/blog/emacs-google-maps-bbdb/</link><guid isPermaLink="true">https://julien.danjou.info/blog/emacs-google-maps-bbdb/</guid><description>Today&apos;s fun idea was to put all my contacts stored into BBDB on a Google Maps&apos; map, using my Google Maps extension for Emacs.</description><pubDate>Wed, 18 Aug 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Today&apos;s fun idea was to put all my contacts stored into &lt;a href=&quot;http://bbdb.sourceforge.net/&quot;&gt;BBDB&lt;/a&gt; on a Google Maps&apos; map, using my Google Maps extension for Emacs.&lt;/p&gt;
&lt;p&gt;With the help of a few lines of Lisp glue:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(google-maps-static-show
 :markers
 (mapcar
  (lambda (address-entry)
    `((,(concat
         (mapconcat
          &apos;identity
          (elt address-entry 1) &quot;, &quot;) &quot;, &quot;
          (elt address-entry 2) &quot;, &quot;
          (elt address-entry 3) &quot;, &quot;
          (elt address-entry 4) &quot;, &quot;
          (elt address-entry 5)))))
  (mapcan
   (lambda (record)
     ;; We need to copy the returned list, because mapcan will modify it later
     (copy-list (bbdb-record-addresses record)))
   (bbdb-records))))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/emacs-google-maps-bbdb-1.png&quot; alt=&quot;Screenshot of Google Maps with BBDB contacts&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It&apos;s really simplistic, but I did not need more to have fun. :-) This could be extended to set a specific marker and/or color for each contact, with a legend. I&apos;ll let that as an exercise for my readers.&lt;/p&gt;
</content:encoded></item><item><title>Update on rainbow-mode</title><link>https://julien.danjou.info/blog/update-on-rainbow-mode/</link><guid isPermaLink="true">https://julien.danjou.info/blog/update-on-rainbow-mode/</guid><description>rainbow-mode had a big success and good feedbacks when I released it for the first time a couple of months ago.</description><pubDate>Tue, 10 Aug 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;rainbow-mode had a big success and good feedbacks when I released it for the first time a couple of months ago.&lt;/p&gt;
&lt;p&gt;Several users asked to me request its inclusion into &lt;a href=&quot;http://www.gnu.org/software/emacs/&quot;&gt;Emacs&lt;/a&gt;. Therefore, some days ago, &lt;a href=&quot;http://lists.gnu.org/archive/html/emacs-devel/2010-07/msg01290.html&quot;&gt;I proposed to merge it inside Emacs trunk&lt;/a&gt;. My request has been denied, but the mode has been added to the &lt;a href=&quot;http://elpa.gnu.org&quot;&gt;Emacs 24 package repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the mean time, I&apos;ve added support for &lt;a href=&quot;http://www.w3.org/TR/css3-color/#hsl-color&quot;&gt;hsl() and hsla()&lt;/a&gt; support, and added&lt;br /&gt;
&lt;a href=&quot;http://www.w3.org/TR/css3-color/#svg-color&quot;&gt;CSS 3/SVG color names&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Porting D-Bus to XCB: story of a failure</title><link>https://julien.danjou.info/blog/porting-dbus-on-xcb/</link><guid isPermaLink="true">https://julien.danjou.info/blog/porting-dbus-on-xcb/</guid><description>Even if I recently stated I lost some of my faith in XCB, I still sometimes hack things to add support for it.</description><pubDate>Thu, 29 Jul 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Even if &lt;a href=&quot;https://julien.danjou.info/blog/thoughts-and-rambling-on-the-x-protocol&quot;&gt;I recently stated I lost some of my faith&lt;/a&gt; in &lt;a href=&quot;http://xcb.freedesktop.org&quot;&gt;XCB&lt;/a&gt;, I still sometimes hack things to add support for it.&lt;/p&gt;
&lt;p&gt;These last days, I&apos;ve worked on a &lt;a href=&quot;http://dbus.freedesktop.org&quot;&gt;D-Bus&lt;/a&gt; port from Xlib to XCB. The port was quite straight forward, since there&apos;s only a little piece of D-Bus using X, which is &lt;code&gt;dbus-launch&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I though D-Bus was a good candidate, since it&apos;s part of the &lt;a href=&quot;http://www.freedesktop.org&quot;&gt;Freedesktop&lt;/a&gt; initiative. Therefore, I was expecting a warm welcome and some enthusiasm from a fellow project.&lt;/p&gt;
&lt;p&gt;My contribution got one useful review, and a &lt;a href=&quot;http://lists.freedesktop.org/archives/dbus/2010-July/013185.html&quot;&gt;cold reply from Thiago Macieira&lt;/a&gt; (a &lt;a href=&quot;http://www.kde.org&quot;&gt;KDE&lt;/a&gt;/&lt;a href=&quot;http://qt.nokia.com&quot;&gt;Qt&lt;/a&gt;/&lt;a href=&quot;http://www.nokia.com&quot;&gt;Nokia&lt;/a&gt; developer):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;No, sorry, I don&apos;t agree..&lt;br /&gt;
I&apos;ve just checked and my Solaris machine doesn&apos;t have XCB.&lt;br /&gt;
Please do not remove the X11 code. You may &lt;em&gt;add&lt;/em&gt; the XCB code, but you cannot remove the X11 code.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is not really the kind of answer I expected, actually. I then reworked the code to &lt;a href=&quot;http://lists.freedesktop.org/archives/dbus/2010-July/013192.html&quot;&gt;please Thiago&lt;/a&gt;, and added some &lt;em&gt;#ifdef&lt;/em&gt; to add XCB support to D-Bus, with a fallback to libx11 where XCB would not be available.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://lists.freedesktop.org/archives/dbus/2010-July/013196.html&quot;&gt;Havoc Pennington replied&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Given that libX11 now uses xcb as backend, I don&apos;t understand the&lt;br /&gt;
value of porting to use libxcb directly when there isn&apos;t an issue of&lt;br /&gt;
round trips or other stuff. It will just make #ifdef hell, while the&lt;br /&gt;
X11 API is an API that works on both xcb and non-xcb platforms.&lt;br /&gt;
Maybe people should be thinking about porting xcb to non-Linux&lt;br /&gt;
platforms? The X protocol should be the same on other UNiX, so xcb in&lt;br /&gt;
theory ought to work fine if you just compiled it on Solaris/BSD, same&lt;br /&gt;
as GTK or dbus or Qt would work fine.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The last part &quot;Maybe people should be thinking about porting xcb to non-Linux platforms?&quot; is still unclear to me, even though &lt;a href=&quot;http://lists.freedesktop.org/archives/dbus/2010-July/013197.html&quot;&gt;I asked Havoc to explain what he meant&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Finally, &lt;a href=&quot;http://lists.freedesktop.org/archives/dbus/2010-July/013198.html&quot;&gt;Thiago refused to merge the patch&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[…] thanks for the patch, but like Havoc I am unsure of the value. We can&apos;t&lt;br /&gt;
drop the X11 codepaths now because too many systems exist without&lt;br /&gt;
XCB. Adding the XCB codepaths only made it more complex, even though you did a good job.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I can&apos;t disagree with that conclusion: using both XCB and X11 make the code unreadable for little gain. That&apos;s why I did replace libx11 by XCB directly in the first version of the patch. On the other hand, D-Bus people does not seems to really care about making their software evolve in the right direction, even if that requires users to upgrade their systems.&lt;/p&gt;
&lt;p&gt;I think D-Bus using and depending on XCB would have been a good point to push adoption of XCB. Unfortunately, it seems you can&apos;t even rely of projects of the same initiative (i.e. Freedesktop) to work together to make things a little bit better.&lt;/p&gt;
&lt;p&gt;After 5 years of existence, XCB is still not so obvious to people, and making it adopt is going to be a challenge for the next years. The upside is that &lt;a href=&quot;http://www.x.org/wiki/Releases/7.6&quot;&gt;new X.org 7.6 will bring XCB with it&lt;/a&gt;, as part of the katamari.&lt;/p&gt;
</content:encoded></item><item><title>M-x google-maps</title><link>https://julien.danjou.info/blog/google-maps-el/</link><guid isPermaLink="true">https://julien.danjou.info/blog/google-maps-el/</guid><description>Since I have started to use Org-mode, I though it was missing something to have appointment locations on a map.</description><pubDate>Mon, 28 Jun 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Since I have started to use &lt;a href=&quot;http://www.orgmode.org&quot;&gt;Org-mode&lt;/a&gt;, I though it was missing something to have appointment locations on a map. Of course, it&apos;s easy to get a &lt;code&gt;LOCATION&lt;/code&gt; property from an entry, and then &lt;code&gt;browse-url&lt;/code&gt; on &lt;a href=&quot;http://maps.google.com&quot;&gt;Google Maps&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/emacs-google-maps-1.png&quot; alt=&quot;emacs-google-maps-1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;But it is &lt;strong&gt;too&lt;/strong&gt; easy for me, so once again I said: challenge accepted! I will bring Google Maps into Emacs!&lt;/p&gt;
&lt;p&gt;After several hours of work, the &lt;a href=&quot;https://github.com/jd/google-maps.el&quot;&gt;google-maps.el project&lt;/a&gt; shows a map!&lt;/p&gt;
&lt;p&gt;It fully implements the &lt;a href=&quot;http://code.google.com/apis/maps/documentation/staticmaps/&quot;&gt;Google Static Maps API&lt;/a&gt; and the &lt;a href=&quot;http://code.google.com/apis/maps/documentation/geocoding/&quot;&gt;Google Maps Geocoding API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can type &lt;code&gt;M-x google-maps&lt;/code&gt; and type some place to see it marked on map. Of course you can do much more, as seen in the screen shot above.&lt;/p&gt;
&lt;p&gt;I&apos;ve also completed all of this with a small &lt;code&gt;org-location-google-maps.el&lt;/code&gt; which simply show a Google Maps&apos; map for the location of an event in &lt;em&gt;Org mode&lt;/em&gt; by pressing &lt;code&gt;C-c M-l&lt;/code&gt; in an Org buffer or in the Org agenda.&lt;/p&gt;
</content:encoded></item><item><title>Announcing rainbow-mode</title><link>https://julien.danjou.info/blog/announcing-rainbow-mode/</link><guid isPermaLink="true">https://julien.danjou.info/blog/announcing-rainbow-mode/</guid><description>While customizing Emacs this last weeks, I had the need to customize also the color theme.</description><pubDate>Wed, 16 Jun 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;While customizing &lt;a href=&quot;http://www.gnu.org/software/emacs/&quot;&gt;Emacs&lt;/a&gt; this last weeks, I had the need to customize also the color theme.&lt;/p&gt;
&lt;p&gt;Color themes are always a pain in the ass to edit, because you&apos;re supposed to read color strings like &lt;em&gt;#aabbcc&lt;/em&gt; and guess what colors they represent.&lt;/p&gt;
&lt;p&gt;This is why I wrote &lt;em&gt;rainbow-mode&lt;/em&gt;, a minor mode for Emacs that will highlight strings that represents color, using the color they represent.&lt;/p&gt;
&lt;p&gt;This support hexadecimal syntax, HTML color name, X color names and rgb() CSS syntax.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://julien.danjou.info/content/images/03/rainbow-mode-1.png&quot; alt=&quot;rainbow-mode-1&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>Desktop notification support for Emacs</title><link>https://julien.danjou.info/blog/desktop-notification-support-for-emacs/</link><guid isPermaLink="true">https://julien.danjou.info/blog/desktop-notification-support-for-emacs/</guid><description>This last weeks, I&apos;ve worked on implementing the Desktop Notification Specification into Emacs.</description><pubDate>Wed, 09 Jun 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This last weeks, I&apos;ve worked on implementing the &lt;a href=&quot;http://www.galago-project.org/specs/notification/&quot;&gt;Desktop Notification Specification&lt;/a&gt; into &lt;a href=&quot;http://www.gnu.org/software/emacs/&quot;&gt;Emacs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It allows sending desktop notification in a very simple way.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(notifications-notify
    :title &quot;You&apos;ve got mail!&quot;
    :body &quot;There&apos;s 34 mails unread&quot;
    :app-icon &quot;~/.emacs.d/icons/mail.png&quot;
    :urgency &apos;low)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It supports the protocol signals (&lt;code&gt;NotificationClosed&lt;/code&gt; and &lt;code&gt;ActionInvoked&lt;/code&gt;) and the two main methods (&lt;code&gt;Notify&lt;/code&gt; and &lt;code&gt;CloseNotification&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The methods specification are implemented entirely (hints, replaces, actions, icon, etc).&lt;/p&gt;
&lt;p&gt;The signals are supported via callbacks function provided on the notification creation.&lt;/p&gt;
&lt;p&gt;It have been merged into Emacs trunk today.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2010-06-09  Julien Danjou  &amp;lt;julien@danjou.info&amp;gt;

	* net/notifications.el: New file.

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This also allowed me to discover, raise and fix a &lt;a href=&quot;http://lists.gnu.org/archive/html/emacs-devel/2010-06/msg00228.html&quot;&gt;bug&lt;/a&gt; in the D-Bus binding of Emacs, which will be probably fixed in trunk soon.&lt;/p&gt;
</content:encoded></item><item><title>Announcing erc-track-score</title><link>https://julien.danjou.info/blog/announcing-erc-track-score/</link><guid isPermaLink="true">https://julien.danjou.info/blog/announcing-erc-track-score/</guid><description>A couple of months ago, I&apos;ve started using ERC to hang out on IRC.</description><pubDate>Mon, 07 Jun 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A couple of months ago, I&apos;ve started using &lt;a href=&quot;http://www.emacswiki.org/emacs/ERC&quot;&gt;ERC&lt;/a&gt; to hang out on &lt;a href=&quot;http://en.wikipedia.org/wiki/Internet_Relay_Chat&quot;&gt;IRC&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&apos;ve read all the pages on &lt;a href=&quot;http://www.emacswiki.org/&quot;&gt;EmacsWiki&lt;/a&gt; about it, just to see how far I could customize it.&lt;/p&gt;
&lt;p&gt;I must admit that I was not disappointed, even if I expected to be. It&apos;s quite a nice software, and once well configured it&apos;s more convenient that my old &lt;a href=&quot;http://www.irssi.org&quot;&gt;irssi&lt;/a&gt; setup.&lt;/p&gt;
&lt;p&gt;While browsing EmacsWiki, I read an interesting idea about channel scoring/temperature on the &lt;a href=&quot;http://www.emacswiki.org/emacs/ErcChannelTracking#toc9&quot;&gt;erc-track&lt;/a&gt; page. The idea is to see if it&apos;s worth jumping to an IRC channel to see what people are talking about.&lt;/p&gt;
&lt;p&gt;Challenge accepted!&lt;/p&gt;
&lt;p&gt;I sat up and started to dig though ERC source code to find the information I needed about variables and functions.&lt;/p&gt;
&lt;p&gt;I finally did write something nice, which I called erc-track-score. And yet another piece of software I wrote for my lovely &lt;a href=&quot;http://www.gnu.org/software/emacs/&quot;&gt;Emacs&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;How does it work? Ha-ha, I was sure you would ask. You&apos;re so predictable, dude! Read the following, and you&apos;ll know everything you ever wanted to know about it since the moment you read the title of that blog entry.&lt;/p&gt;
&lt;p&gt;Which probably turned you on.&lt;/p&gt;
&lt;p&gt;Nasty you.&lt;/p&gt;
&lt;p&gt;First of all, the score of a channel starts at zero. Zero means &quot;seriously, don&apos;t bother, nothing is happening here&quot;.&lt;/p&gt;
&lt;p&gt;Upon each new message arrival, the score is incremented by 1. If a new message contains a keyword, your nickname or is sent by a pal, the score is increased by configurable values, by default between 2 and 20 points, depending on the match type. On the other hand, when a message is send by some fool, the score is decreased by 1 by default.&lt;/p&gt;
&lt;p&gt;Obviously, if the score is going negative, you really should not jump to the channel.&lt;/p&gt;
&lt;p&gt;Finally, the score is permanently and slowly brought back to 0. By default, the score is decreased by 1 point every 10 seconds.&lt;/p&gt;
&lt;p&gt;Overall, reading the score should gives you a good idea of the channel temperature.&lt;/p&gt;
&lt;p&gt;I&apos;m still not sure what is the best formula to compute the score, but so far the default values seem quite good. We&apos;ll see.&lt;/p&gt;
</content:encoded></item><item><title>Thoughts and rambling on the X protocol</title><link>https://julien.danjou.info/blog/thoughts-and-rambling-on-the-x-protocol/</link><guid isPermaLink="true">https://julien.danjou.info/blog/thoughts-and-rambling-on-the-x-protocol/</guid><description>Two years ago, while working on awesome, I joined the Freedesktop initiative to work on XCB. I had to learn the arcane of the X11 protocol and all the mysterious and old world that goes with it.</description><pubDate>Tue, 01 Jun 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Two years ago, while working on &lt;a href=&quot;http://awesome.naquadah.org&quot;&gt;awesome&lt;/a&gt;, I joined the &lt;a href=&quot;http://www.freedesktop.org&quot;&gt;Freedesktop&lt;/a&gt; initiative to work on &lt;a href=&quot;http://xcb.freedesktop.org&quot;&gt;XCB&lt;/a&gt;. I had to learn the arcane of the X11 protocol and all the mysterious and old world that goes with it.&lt;/p&gt;
&lt;p&gt;Now that I&apos;ve swum all this months in this mud, I just feel like I need to share my thoughts about what become a mess over the decades.&lt;/p&gt;
&lt;h2&gt;When I was unborn…&lt;/h2&gt;
&lt;p&gt;…the &lt;a href=&quot;http://en.wikipedia.org/wiki/Toto_(band)&quot;&gt;Toto&lt;/a&gt; band were releasing their &lt;a href=&quot;http://en.wikipedia.org/wiki/Africa_(Toto_song)&quot;&gt;song &quot;Africa&quot;&lt;/a&gt; and some smart guys were working on a windowing system: the X Window System (this is its full name) which therefore has a (too) long history. The latest version of its protocol, the 11th one, has been designed in the 80&apos;s. You can learn more about the history in the &lt;a href=&quot;http://en.wikipedia.org/wiki/X_Window_System&quot;&gt;Wikipedia article about X&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In 2010, we still listen disco music and we still use various protocols designed in the 80&apos;s and even before X. Music have evolved, protocols have evolved, and so did X11.&lt;/p&gt;
&lt;p&gt;The problem is that X11 did not evolve that well. The guys at MIT-and-some other-places-with-very-smart-people-in-it created X version 1 in 1984, and updated it until X version 11 (the one we&apos;re still using) in 1987. Eleven version in 3 years, that was following the &quot;release early, release often&quot; model. But I don&apos;t know why, it just stopped to happen for the last 23 years (that&apos;s not totally true: they added (and then deprecated) many extensions.)&lt;/p&gt;
&lt;p&gt;I don&apos;t know what changes have been made in the first 11 major versions of the X protocol, but I&apos;m rather sure we should have deserve a couple of major version updates this last 2 decades.&lt;/p&gt;
&lt;p&gt;In my humble opinion, X11 was not designed to live 23 years. But hey, I&apos;m not blaming anyone here: I was 4 years old and playing Lego® when they released this latest version of the X protocol, so there is little chance I&apos;d have done something better.&lt;/p&gt;
&lt;h2&gt;We won&apos;t fix. We&apos;ll work-around.&lt;/h2&gt;
&lt;p&gt;That is probably one of the guideline of the X protocol for the last years. And don&apos;t misread me: I&apos;m not bashing anyone thereafter.&lt;/p&gt;
&lt;p&gt;Since the X11 protocol was aging, the X guys started to add &lt;a href=&quot;http://en.wikipedia.org/wiki/X_Window_System_protocols_and_architecture#Extensions&quot;&gt;extensions&lt;/a&gt;. They added tons of them over the years. This, in application of one of &lt;a href=&quot;http://en.wikipedia.org/wiki/X_Window_System_protocols_and_architecture#Design_principles&quot;&gt;the early principles of X&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It is as important to decide what a system is not as to decide what it&lt;br /&gt;
is. Do not serve all the world&apos;s needs; rather, make the system extensible&lt;br /&gt;
so that additional needs can be met in an upwardly compatible fashion.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;All of them with no exception were added because, bad luck, the X11 protocol did not anticipated the things that happened in the last 23 years, like video, OpenGL, multiple monitors, or the pleasure to draw oval windows. Some of this extensions are still in use, while some of them have been dropped.&lt;/p&gt;
&lt;p&gt;While this is not a bad thing to extends the protocol, it seems like a bad thing to try to fix the protocol with for example the &lt;a href=&quot;http://en.wikipedia.org/wiki/XFixes&quot;&gt;XFixes extension&lt;/a&gt;, even with all the good intentions Keith Packard might have in his greatness.&lt;/p&gt;
&lt;h2&gt;Actually it&apos;s even worst than you think&lt;/h2&gt;
&lt;p&gt;The X11 protocol (without extensions) defines about 120 types of requests: create a window, move a window, etc.&lt;/p&gt;
&lt;p&gt;Nowadays, there&apos;s at least 25 % of them which are useless: usage of server-side font, or the drawing of squares and polygon, are unused by any modern application or toolkit. All of this is superseded by requests from extensions, like the &lt;a href=&quot;http://en.wikipedia.org/wiki/XRender&quot;&gt;XRender&lt;/a&gt; one.&lt;/p&gt;
&lt;p&gt;The handling of multiple monitors displays has totally been screwed up. X11 has been designed to work in &lt;a href=&quot;http://en.wikipedia.org/wiki/Zaphod_Beeblebrox#Cultural_references&quot;&gt;Zaphod&lt;/a&gt; mode (independent monitors). But &lt;a href=&quot;http://en.wikipedia.org/wiki/Xinerama&quot;&gt;Xinerama&lt;/a&gt;, and nowadays &lt;a href=&quot;http://en.wikipedia.org/wiki/XRandR&quot;&gt;XRandR&lt;/a&gt; have replaced it up: recent X servers (released after ~2007) does not support Zaphod mode anymore, even if it&apos;s a core piece of the X11 protocol.&lt;/p&gt;
&lt;p&gt;Worst: on many requests, there&apos;s limitation or design flaws, like described in this document: &lt;a href=&quot;http://www.std.org/~msm/common/protocol.pdf&quot;&gt;Why X Is Not Our Ideal Window System&lt;/a&gt; by DEC researchers.&lt;/p&gt;
&lt;h2&gt;We&apos;ll add more broken standard on top of that&lt;/h2&gt;
&lt;p&gt;Following &lt;a href=&quot;http://en.wikipedia.org/wiki/X_Window_System_protocols_and_architecture#Design_principles&quot;&gt;its early principle&lt;/a&gt;, X does not define policies but only mechanisms, which seems like a good thing,&lt;/p&gt;
&lt;p&gt;Consequently, people started writing specifications to determine a number of stuff and dogmas: &lt;a href=&quot;http://en.wikipedia.org/wiki/ICCCM&quot;&gt;ICCCM&lt;/a&gt;. That was 22 years ago in 1988. It&apos;s useless to add that many things in this specification are now obsolete, useless, or that it misses many modern stuff.&lt;/p&gt;
&lt;p&gt;I was not the only one to think that. The people from what will be the major desktop environments, &lt;a href=&quot;http://www.kde.org&quot;&gt;KDE&lt;/a&gt; and &lt;a href=&quot;http://www.gnome.org&quot;&gt;GNOME&lt;/a&gt;, saw that too in the 90&apos;s while I was learning to count. So they wrote &lt;a href=&quot;http://en.wikipedia.org/wiki/Extended_Window_Manager_Hints&quot;&gt;EWMH&lt;/a&gt;, another standard that comes on top of ICCCM and extends it with nifty features like maximization, full screen mode, etc.&lt;/p&gt;
&lt;p&gt;The problem is that this standard has also been written by narrow-minded people who at that time, were working on GNOME or KDE (and maybe others). This desktop environments were having and still have some strong concepts of how should work a desktop: &quot;it should have work-spaces&quot;, &quot;a window is only on one workspace&quot;, &quot;we only see a workspace at a time&quot;, &quot;you do not have multiple screens&quot;, etc.&lt;/p&gt;
&lt;h2&gt;Dude, we don&apos;t care: we have toolkits!&lt;/h2&gt;
&lt;p&gt;This vision of how the desktop should work have now been written in marble in all applications and libraries implementing EWMH, like &lt;a href=&quot;http://www.gtk.org&quot;&gt;GTK+&lt;/a&gt; or &lt;a href=&quot;http://qt.nokia.com&quot;&gt;Qt&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Nowadays, everybody forgot about all of this standards. Toolkits have implemented this, circumvented the X11 protocol limitation and flaws, and nobody wants to look back.&lt;/p&gt;
&lt;p&gt;Like all standards, obviously some people implemented them badly. This had some side effects, like &lt;a href=&quot;https://julien.danjou.info/blog/openoffice-better-as-a-pager&quot;&gt;OpenOffice acting like a pager&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;We don&apos;t look back? Worst, we forgot where we came from!&lt;/h2&gt;
&lt;p&gt;With all these layers of bad designed standards, the desktop continued to evolve for more than a decade. They continued to add more standard, the more recent ones being based on D-Bus like the &lt;a href=&quot;http://www.galago-project.org/specs/notification/&quot;&gt;Desktop Notification Specification&lt;/a&gt; or the latest &lt;a href=&quot;http://www.notmart.org/misc/statusnotifieritem/index.html&quot;&gt;Status Notifier Specification&lt;/a&gt; developed by KDE.&lt;/p&gt;
&lt;p&gt;The Status Notifier is a new implementation of the good old system tray based on &lt;a href=&quot;http://en.wikipedia.org/wiki/D-Bus&quot;&gt;D-Bus&lt;/a&gt; and &lt;a href=&quot;http://en.wikipedia.org/wiki/XEmbed&quot;&gt;XEmbed&lt;/a&gt; instead of the X11 mechanisms, and adding the possibility to show the system tray with something else than icons.&lt;/p&gt;
&lt;p&gt;This specification draft saw an important issue and design flaw raised by Wolfgang Draxinger in &lt;a href=&quot;http://lists.freedesktop.org/archives/xdg/2010-May/011516.html&quot;&gt;this thread on the XDG mailing-list&lt;/a&gt;. What Wolfgang points out, is that X is network-oriented, and D-Bus is not. Therefore, making the Status Notifier specification to use D-Bus to pass system tray messages around is a bad idea, since running a X application from host A on host B will draw the system tray on the wrong host!&lt;/p&gt;
&lt;p&gt;Apparently, reading the thread, this &lt;a href=&quot;http://lists.freedesktop.org/archives/xdg/2010-May/011531.html&quot;&gt;does not fear some of the KDE people&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;of course this is a bizarre corner case not worth much thought. at least&lt;br /&gt;
that&apos;s what you&apos;ll think until you actually run into it yourself (be it&lt;br /&gt;
because you are testing something or because you are setting up some&lt;br /&gt;
weird kiosk environment).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What Oswald describes as a corner case is an actual common use case for many of us. Of course, YMMV.&lt;/p&gt;
&lt;p&gt;From my point of view, this is a step back in the wrong direction. But we can conclude that the network part of X is now worthless, to at least KDE.&lt;/p&gt;
&lt;h2&gt;I used to believe in XCB&lt;/h2&gt;
&lt;p&gt;When I joined Freedesktop, it was to work on XCB, the X C Binding. XCB is a nice, clean, 21st century technology based API to play with the X11 protocol. Its code is auto generated based on XML file describing the protocol.&lt;/p&gt;
&lt;p&gt;In comparison, Xlib is 80&apos;s obfuscated code with almost no comments and hard-coded things. Only a few people can understand some of its corner like its i18n or XKB implementations. And all its code is &lt;em&gt;synchronous&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;For people not knowing it yet, X is a network protocol where you send request (like a GET in HTTP) and then get a response. Xlib forces the application to wait for the reply to its request, so the application is blocked until the X server sends the reply to the request. XCB on the other hand does not block and allows the application to send a batch of requests, do some other stuff in the mean time, and then gets the replies.&lt;/p&gt;
&lt;p&gt;It&apos;s like your Web browser would send one request at a time to a Web server, and would wait until you downloaded all the images one by one to display the page.&lt;/p&gt;
&lt;p&gt;In cases where X and all its clients are on the same host, the latency is small and not really visible, therefore the gain for XCB to be asynchronous is small. On slow network however, the gain can be huge, as proved in the &lt;a href=&quot;http://bugs.freedesktop.org/show_bug.cgi?id=4232&quot;&gt;rewrite of xlsclients with XCB by Peter Harris&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One of the long standing goal of the XCB folks is to kick-out Xlib, to increase speed and hides latency in X11 applications. That requires to port many libraries, because almost none of them (&lt;a href=&quot;http://www.cairographics.org&quot;&gt;Cairo&lt;/a&gt; being an exception) supports XCB.&lt;/p&gt;
&lt;p&gt;From where I stand, I don&apos;t really see if the work is worth it now. The desktop world is trusted by GNOME and KDE, meaning GTK+ and Qt. It seems none of this toolkits are interested to work on XCB, neither on the X protocol. They probably put hard effort in bypassing X limitation and flaws, and they now sit on top of crap of workarounds and broken-by-design-standard implementation. It seems to me they don&apos;t want to go back in the layers and improves things.&lt;/p&gt;
&lt;p&gt;They&apos;re too high to go back down and they don&apos;t see what the gain would be.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.enlightenment.org&quot;&gt;Enlightenment&lt;/a&gt; with its EFL was the first toolkit to have an XCB back-end with the work of Vincent Torri. Unfortunately, the back-end is not maintained and nobody cares about it. Last time I tried it, it did not compile at all.&lt;/p&gt;
&lt;h2&gt;X12?&lt;/h2&gt;
&lt;p&gt;There&apos;s a page called &lt;a href=&quot;http://www.x.org/wiki/Development/X12&quot;&gt;X12&lt;/a&gt; on the Freedesktop wiki, listing all the things that should be fixed some days. Unfortunately, the list continues to grow up an no one talks about working on X12.&lt;/p&gt;
&lt;p&gt;On the other hand, there&apos;s a handset of people trying to work when they will have time on &lt;a href=&quot;http://www.freedesktop.org/wiki/Software/XKeyboardConfig/XKB2Dreams&quot;&gt;XKB2&lt;/a&gt;, the second version of the &quot;let&apos;s-try-to-fix-up-the-keyboard-part-of-the-protocol-we-wrote-23-years-ago-a-second-time&quot; extension.&lt;/p&gt;
&lt;p&gt;To me, it does not seem X12 will happen in the next decade neither.&lt;/p&gt;
&lt;h2&gt;Alternative?&lt;/h2&gt;
&lt;p&gt;Do we got alternative to X? There&apos;s &lt;a href=&quot;http://en.wikipedia.org/wiki/Wayland_(display_server)&quot;&gt;Wayland&lt;/a&gt;, but it&apos;s far from being usable. There&apos;s &lt;a href=&quot;http://www.directfb.org/&quot;&gt;DirectFB&lt;/a&gt;, but that&apos;s not very portable. None seems candidate to replace X some days to me.&lt;/p&gt;
&lt;p&gt;Anyhow, none of the main toolkits around support this alternative. GTK+ once supported DirectFB, but as far as I know, it is not supported nor works nowadays, as stated by &lt;a href=&quot;http://np237.livejournal.com/27459.html&quot;&gt;Josselin Mouette&lt;/a&gt;. This is why recent versions of the Debian installer have migrated to X for the graphic part, thanks to Cyril Brulebois work.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;XCB has been around for more than half-a-decade, and very few people showed interested in it. As far as I can see, nobody is interested to use the X protocol and everybody tries to encapsulate it in some higher-level API as soon as possible to stop seeing it. This leads to poorly written application and toolkits, with a lot of ugly hack.&lt;/p&gt;
&lt;p&gt;All of that also means that starting to write applications and graphical toolkits based on XCB would be a very interesting project, but that would lead to spend too much time learning to circumvent the X protocol flaws, things that have been done in years by predecessors like Qt and GTK+.&lt;/p&gt;
&lt;p&gt;Major toolkits implementations have almost nothing to win in going back in the dark water of X. I guess most of their folks prefer to work on shiny 3D effects based on your GPS location, rather than redefining better basis for everyone.&lt;/p&gt;
&lt;p&gt;The manpower available in the X world is very small. Debian lacking of X maintainers is just the summit of that. There is very smart and very competent and skilled guys in the X world, as you can see by simply reading blog posts on &lt;a href=&quot;http://planet.freedesktop.org&quot;&gt;Planet Freedesktop&lt;/a&gt; for example (me excluded). Unfortunately, there&apos;s not enough of them to cover all the things involved in X: input devices, graphics devices, new protocol extension specification and so on. The X server is really late, and it seems most of the developers prefers to work on the server itself than on the protocol behalf. Which is understandable.&lt;/p&gt;
&lt;p&gt;I&apos;m curious to see where all of that will lead in the upcoming years. I&apos;ve been walking in the X world hallways for about 3 years now, and I feel desktop alternatives to KDE and GNOME will all die sooner or later. The time were you could choose between a dozen &quot;modern&quot; window managers has passed away.&lt;/p&gt;
&lt;p&gt;After all, maybe that is simply Darwinism applied to computer software.&lt;/p&gt;
</content:encoded></item><item><title>Making startup-notification XCB native</title><link>https://julien.danjou.info/blog/making-startup-notification-xcb-native/</link><guid isPermaLink="true">https://julien.danjou.info/blog/making-startup-notification-xcb-native/</guid><description>I&apos;m trying to work on XCB this week. And today I&apos;ve started to accomplish the second step of a long term goal: making an X11 only library using XCB as its primary interface instead of Xlib.</description><pubDate>Mon, 24 May 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;m trying to work on XCB this week. And today I&apos;ve started to accomplish the second step of a long term goal: making an X11 only library using &lt;a href=&quot;http://xcb.freedesktop.org&quot;&gt;XCB&lt;/a&gt; as its primary interface instead of Xlib.&lt;/p&gt;
&lt;p&gt;Last year, I had extended the API of &lt;a href=&quot;http://www.freedesktop.org/wiki/Software/startup-notification&quot;&gt;startup-notification&lt;/a&gt; to support XCB as a back-end. This had been made possible by factorizing some code, duplicating the X11 code and translating it into equivalent XCB.&lt;/p&gt;
&lt;p&gt;Today, I&apos;ve accomplished the second step, being dropping the Xlib code inside startup-notification to keep only the XCB one.&lt;/p&gt;
&lt;p&gt;For this, I used the x11-xcb library, which is available when Xlib is compiled with XCB as its transport, which is nowadays the standard.&lt;/p&gt;
&lt;p&gt;This library provides the function &lt;code&gt;XGetXCBConnection&lt;/code&gt;, which can convert a &lt;code&gt;Display&lt;/code&gt; pointer to a &lt;code&gt;xcb_connection_t&lt;/code&gt; pointer. Consequently, it&apos;s now possible to write and execute XCB based code and being compatible with Xlib.&lt;/p&gt;
&lt;p&gt;I&apos;ve made some benchmark of my work for the occasion, in order to measure what the gain is.&lt;/p&gt;
&lt;p&gt;The first table described 1000 launches of a fake application (a modified version of the startup-notification test suite actually). The X server is local (the latency is very minimal then). The gain is computed between the same back-end type for the total time. &lt;strong&gt;Full XCB&lt;/strong&gt; is the &quot;version&quot; I&apos;m working on.&lt;/p&gt;
&lt;p&gt;Version - Back-end&lt;/p&gt;
&lt;p&gt;User time (seconds)&lt;/p&gt;
&lt;p&gt;Kernel time (seconds)&lt;/p&gt;
&lt;p&gt;Total time (seconds)&lt;/p&gt;
&lt;p&gt;Gain&lt;/p&gt;
&lt;p&gt;0.10 - libx11&lt;/p&gt;
&lt;p&gt;3.20&lt;/p&gt;
&lt;p&gt;7.42&lt;/p&gt;
&lt;p&gt;12.989&lt;/p&gt;
&lt;p&gt;-&lt;/p&gt;
&lt;p&gt;0.10 - libxcb&lt;/p&gt;
&lt;p&gt;2.76&lt;/p&gt;
&lt;p&gt;7.36&lt;/p&gt;
&lt;p&gt;12.414&lt;/p&gt;
&lt;p&gt;-&lt;/p&gt;
&lt;p&gt;Full XCB - libx11&lt;/p&gt;
&lt;p&gt;2.74&lt;/p&gt;
&lt;p&gt;7.50&lt;/p&gt;
&lt;p&gt;12.380&lt;/p&gt;
&lt;p&gt;4.6 %&lt;/p&gt;
&lt;p&gt;Full XCB - libxcb&lt;/p&gt;
&lt;p&gt;2.72&lt;/p&gt;
&lt;p&gt;7.16&lt;/p&gt;
&lt;p&gt;12.037&lt;/p&gt;
&lt;p&gt;3.0 %&lt;/p&gt;
&lt;p&gt;The user time and kernel time are provided but are not really interesting. XCB does not offers a big gain in CPU execution time, but is more about latency. Anyhow, there&apos;s always a gain with XCB.&lt;/p&gt;
&lt;p&gt;This second table describe the same test but running only 100 times over a slow network.&lt;/p&gt;
&lt;p&gt;Version - Back-end&lt;/p&gt;
&lt;p&gt;Total time (seconds)&lt;/p&gt;
&lt;p&gt;Gain&lt;/p&gt;
&lt;p&gt;0.10 - libx11&lt;/p&gt;
&lt;p&gt;76&lt;/p&gt;
&lt;p&gt;-&lt;/p&gt;
&lt;p&gt;0.10 - libxcb&lt;/p&gt;
&lt;p&gt;35&lt;/p&gt;
&lt;p&gt;-&lt;/p&gt;
&lt;p&gt;Full XCB - libx11&lt;/p&gt;
&lt;p&gt;72&lt;/p&gt;
&lt;p&gt;5.2 %&lt;/p&gt;
&lt;p&gt;Full XCB - libxcb&lt;/p&gt;
&lt;p&gt;33&lt;/p&gt;
&lt;p&gt;5.7%&lt;/p&gt;
&lt;p&gt;The gain is relatively small, about 5 %. But anyhow, there&apos;s still a gain. Note that the difference between the execution time of the same test written in XCB and Xlib is just huge. I&apos;ve tried to optimize the Xlib test, but I did not manage to win more seconds.&lt;/p&gt;
&lt;p&gt;In conclusion, considering that startup-notification is only used when an application launches another application, the perceivable gain might be even smaller. But anyhow, I think it&apos;s worth it.&lt;/p&gt;
</content:encoded></item><item><title>Announcing muse-blog</title><link>https://julien.danjou.info/blog/announcing-muse-blog/</link><guid isPermaLink="true">https://julien.danjou.info/blog/announcing-muse-blog/</guid><description>Digging into the fabulous world of Emacs and Lisp, I wanted to use it to build my personal Web site and my blog.</description><pubDate>Wed, 19 May 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Digging into the fabulous world of Emacs and Lisp, I wanted to use it to build my personal Web site and my blog.&lt;/p&gt;
&lt;p&gt;I already moved from &lt;a href=&quot;http://ikiwiki.info/&quot;&gt;ikiwiki&lt;/a&gt; to &lt;a href=&quot;http://mwolson.org/projects/EmacsMuse.html&quot;&gt;Emacs Muse&lt;/a&gt; for my HTML pages some weeks ago.&lt;/p&gt;
&lt;p&gt;Muse provides an extension to maintain a journal, called &lt;em&gt;muse-journal&lt;/em&gt;. Unfortunately, it was far to fulfill all my needs, and I decided that it would be a good exercise to write a better extension.&lt;/p&gt;
&lt;p&gt;Consequently, I started to wrote my own extension, which I named muse-blog.&lt;/p&gt;
&lt;p&gt;And this is now what is used to build this blog. :-)&lt;/p&gt;
</content:encoded></item><item><title>Entering the Emacs world</title><link>https://julien.danjou.info/blog/entering-the-emacs-world/</link><guid isPermaLink="true">https://julien.danjou.info/blog/entering-the-emacs-world/</guid><description>In February 2009, my friend dim tried to force me using Emacs. I know a couple of people using it and Gnus for reading their mail, and it always made me curious.</description><pubDate>Mon, 17 May 2010 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In February 2009, my friend &lt;a href=&quot;http://tapoueh.org&quot;&gt;dim&lt;/a&gt; tried to force me using Emacs. I know a couple of people using it and &lt;a href=&quot;http://www.gnus.org&quot;&gt;Gnus&lt;/a&gt; for reading their mail, and it always made me curious.&lt;/p&gt;
&lt;p&gt;At that time, more than a year ago, Emacs 22 and Gnus did not seem usable from my point of view.&lt;/p&gt;
&lt;p&gt;But around mid February, with the help of dim, I tried again to start using Emacs.&lt;/p&gt;
&lt;p&gt;Actually, this was not something new for me. I (very basically) used Emacs between 2000 and 2006. In 2006, when I finished the university and started working at &lt;a href=&quot;http://www.easter-eggs.com&quot;&gt;Easter-eggs&lt;/a&gt;, I met a couple of &lt;a href=&quot;http://www.vim.org&quot;&gt;vim&lt;/a&gt; enthusiasts. They taught me how to use it in various ways, and I started to know more about vim than Emacs, so I switched.&lt;/p&gt;
&lt;p&gt;This time, I started by configuring it, but reading the manual and also learning a bit of Lisp. It took me several weeks, but step by step I learned many, many things. And I must admit, I liked it.&lt;/p&gt;
&lt;p&gt;I&apos;ve configured and starting to use some very important mode, like Gnus, &lt;a href=&quot;http://orgmode.org&quot;&gt;Org mode&lt;/a&gt;, &lt;a href=&quot;http://mwolson.org/projects/EmacsMuse.html&quot;&gt;Muse&lt;/a&gt;, or even ERC.&lt;/p&gt;
&lt;p&gt;I&apos;ll probably talk about various Emacs related things in the near future, since I already wrote more than a thousand lines of Lisp in the last 2 months.&lt;/p&gt;
&lt;p&gt;Anyhow, I&apos;d just conclude by asserting that my new Emacs/Gnus/Org/ERC setup beats my old vim/mutt/nothing/irssi to the death with a baseball bat. :-)&lt;/p&gt;
</content:encoded></item><item><title>Python cairo and XCB support</title><link>https://julien.danjou.info/blog/python-cairo-and-xcb-support/</link><guid isPermaLink="true">https://julien.danjou.info/blog/python-cairo-and-xcb-support/</guid><description>cairo has a Python binding (pycairo) since a long time, and some months ago a Python binding for XCB (xpyb) has been released.</description><pubDate>Tue, 22 Dec 2009 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;http://www.cairographics.org&quot;&gt;cairo&lt;/a&gt; has a &lt;a href=&quot;http://www.cairographics.org/pycairo/&quot;&gt;Python binding (pycairo)&lt;/a&gt; since a long time, and some months ago a &lt;a href=&quot;http://cgit.freedesktop.org/xcb/xpyb/&quot;&gt;Python binding for XCB (xpyb)&lt;/a&gt; has been released.&lt;/p&gt;
&lt;p&gt;Pycairo has no support for creating Xlib surfaces. You can get a Xlib surface from PyGTK and then use Pycairo to draw on it, but there&apos;s no way to create one directly.&lt;/p&gt;
&lt;p&gt;What I&apos;ve done is make Pycairo aware of xpyb so it can creates directly an XCB surface from a XCB connection and a drawable.&lt;/p&gt;
&lt;p&gt;As said in &lt;a href=&quot;http://lists.freedesktop.org/archives/xcb/2009-December/005438.html&quot;&gt;my mail to the XCB list&lt;/a&gt;, I&apos;m now waiting for a review before pushing this upstream. :-)&lt;/p&gt;
&lt;p&gt;For the first time, I guess, XCB has beaten Xlib support! ;-)&lt;/p&gt;
</content:encoded></item><item><title>Teething troubles</title><link>https://julien.danjou.info/blog/teething-troubles/</link><guid isPermaLink="true">https://julien.danjou.info/blog/teething-troubles/</guid><description>It&apos;s not that often that I start something from scratch. It&apos;s an amazing feeling to start a new project, to start writing something new. I like that. It&apos;s creation, it&apos;s an artistic part of our comput</description><pubDate>Sun, 20 Dec 2009 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It&apos;s not that often that I start something from scratch. It&apos;s an amazing feeling to start a new project, to start writing something new. I like that. It&apos;s creation, it&apos;s an artistic part of our computing stuff. I feel like a code artist.&lt;/p&gt;
&lt;p&gt;And what I like even more is that little feeling that you are going in an unknown land. Some area in this tech world where nobody ever came before you, or only a few pioneers.&lt;/p&gt;
&lt;p&gt;That&apos;s the sensation I got starting to using &lt;a href=&quot;http://www.cython.org&quot;&gt;Cython&lt;/a&gt;, &lt;a href=&quot;http://www.python.org&quot;&gt;Python 3&lt;/a&gt; and various other tools. I just spent half of my time trying to fix problems, rather than working on &lt;em&gt;my&lt;/em&gt; code. Problems in autoconf macro not knowing Python 2.6 or Python 3.1. Problems and limitations in Cython. And problem in Python.&lt;/p&gt;
&lt;p&gt;That last one was a hard one. I&apos;m still a beginner in the Python world: I barely know anything. And I was trying to use something nobody never did: building an embedded Python with a set of built-in modules.&lt;/p&gt;
&lt;p&gt;I spent hours trying to find why one type of module importing was badly failing. I finally found the answer thanks to a guy. who has the same problem A guy ? No. A pioneer. What do I say? A hero. He&apos;s been my week-hero! Thank you Miguel Lobo because you found the bug I chased for hours and because you even reported it as &lt;a href=&quot;http://bugs.python.org/issue1644818&quot;&gt;issue 1644818&lt;/a&gt;, including a patch! How not damn wonderful is that?&lt;/p&gt;
&lt;p&gt;I will not bore you with the technical details of that bug, since nobody cares. Nobody cares, even the Python guys, since that bug has been opened for 3 years, and nobody even reviewed in that time. I found an old thread about that bug where some guys were wanking about how they should do the review, because Miguel pushed for several weeks to have a review, back in 2007.&lt;/p&gt;
&lt;p&gt;But that bug was in my way. I had to do something. So I prepared my mail reader, mounted my web browser and here I was for a uniq quest: getting a Python bug fixed.&lt;/p&gt;
&lt;p&gt;At that point, if you did not stop reading earlier, you might get very excited. Don&apos;t be, spoiler, it&apos;s still not fixed. You&apos;ll have to wait the end of the season and see all the episodes I&apos;ll have to write to get the end of the story!&lt;/p&gt;
&lt;p&gt;Let&apos;s continue.&lt;/p&gt;
&lt;p&gt;I had to create an account on the Python bug tracking system. That was a trivial task for a man like me (you bet). Then, I launched a verbal attack, something you rarely see in a bug tracking system. Something I knew would awake any developer caring about their software.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Julien Danjou:&lt;br /&gt;
Is there any chance to see this &lt;em&gt;bug&lt;/em&gt; fixed someday?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I had the deep feeling that my quest was starting here. How many days would I have to wait until I get an answer? Time was passing. Minutes were ticking while I was waiting, sat in a comfortable sofa in a softly lighted room. It seemed like all my life was shorter than the delay I had to wait to get an answer.&lt;/p&gt;
&lt;p&gt;After waiting for hours, suddenly, and only 15 minutes later, I got an answer:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Martin v. Löwis:&lt;br /&gt;
Please ask on python-dev. I may be willing to revive my five-for-one offer.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Martin? Don&apos;t know that guy. Who is he? Who is he like? Will he fix that bug? What is this offer? So many question without an answer. But he asked to ask on python-dev, and I said: challenged accepted! I will write a mail to python-dev to get that bug fixed.&lt;/p&gt;
&lt;p&gt;Which I did. I sent a short (but well written you know, I made efforts) &quot;WTF?&quot; to pyhon-dev.&lt;/p&gt;
&lt;p&gt;And then the guy asked me to review 5 bugs so he will review and fix this one. And this is how I said that he was pissing me off for blackmailing me to fix a bug that was its &quot;duty&quot;.&lt;/p&gt;
&lt;p&gt;Therefore, this is the end of the story so far. Will that bug be fixed some day? There&apos;s a hope, because another guy jumped in and took the bug assignment.&lt;/p&gt;
&lt;p&gt;To be continued.&lt;/p&gt;
&lt;p&gt;My conclusion about all that story: that is a little rude to start something new, with new tools, and get quickly into teething troubles. It&apos;s even more harsh to enter a community because you just found bugs, and be not very well received when you ask to apply a 10 lines long fix somebody wrote 3 years ago to fix it.&lt;/p&gt;
&lt;p&gt;I&apos;ll probably still use Python :-), but I get a darker image of its community now.&lt;/p&gt;
</content:encoded></item><item><title>Courier to Dovecot migration</title><link>https://julien.danjou.info/blog/courier-to-dovecot-migration/</link><guid isPermaLink="true">https://julien.danjou.info/blog/courier-to-dovecot-migration/</guid><description>This week, I&apos;ve managed to migrate from courier-imap to dovecot at work. I always had a good experience with dovecot, and I still have one.</description><pubDate>Fri, 02 Oct 2009 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This week, I&apos;ve managed to migrate from &lt;a href=&quot;http://www.courier-mta.org/imap/&quot;&gt;courier-imap&lt;/a&gt; to &lt;a href=&quot;http://www.dovecot.org&quot;&gt;dovecot&lt;/a&gt; at work. I always had a good experience with dovecot, and I still have one.&lt;/p&gt;
&lt;p&gt;Dovecot performances are very good in comparison with courier. With that switch, we dropped the CPU usage of the server from 25 % to 10 %, and it&apos;s damn faster now. I have no idea why, but I think that it&apos;s better written looking at the code, and also that its usage of index files helps a lot.&lt;/p&gt;
&lt;p&gt;We got no problem getting things work with public folders either, so the switch was almost painless.&lt;/p&gt;
&lt;p&gt;The only problem we had is that Dovecot is too smart for some MUA. Consequently, we hit an &lt;a href=&quot;http://dev.mutt.org/trac/ticket/969&quot;&gt;8 years old Mutt bug #969&lt;/a&gt;, which I also reported to the Debian BTS as &lt;a href=&quot;http://bugs.debian.org/549204&quot;&gt;#549204&lt;/a&gt; with a not-well-tested-but-seems-to-work patch.&lt;/p&gt;
&lt;p&gt;Thanks to &lt;a href=&quot;http://www.claws-mail.org/&quot;&gt;Claws mail&lt;/a&gt;, we also found a &lt;a href=&quot;http://dovecot.org/pipermail/dovecot/2009-October/043236.html&quot;&gt;bug in dovecot 1.2.5&lt;/a&gt;, which should be fixed soon. Dovecot upstream is very responsive and that&apos;s always something nice to know when you use a free software.&lt;/p&gt;
</content:encoded></item><item><title>Various news: what happend during summer</title><link>https://julien.danjou.info/blog/various-news/</link><guid isPermaLink="true">https://julien.danjou.info/blog/various-news/</guid><description>It&apos;s been a while since I blogged about something. So here&apos;s a bunch of things I&apos;ve done the last month.  Holidays Well, I&apos;ve been in holidays one week. :-P  awesome There have been a huge number of c</description><pubDate>Tue, 22 Sep 2009 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It&apos;s been a while since I blogged about something. So here&apos;s a bunch of things I&apos;ve done the last month.&lt;/p&gt;
&lt;h2&gt;Holidays&lt;/h2&gt;
&lt;p&gt;Well, I&apos;ve been in holidays one week. :-P&lt;/p&gt;
&lt;h2&gt;awesome&lt;/h2&gt;
&lt;p&gt;There have been a huge number of changes between 3.3 (released in June) and 3.4 (almost relesed). I wrote a small but very useful object layer on top of Lua, which adds a class/object system a bit like &lt;a href=&quot;http://www.gtk.org&quot;&gt;gobject&lt;/a&gt;. I&apos;ve also replaced all the hooks by per-class/object signals. Finally, the awesome Lua basement are cleaner than they were before, and the extendability is improved. How nice.&lt;/p&gt;
&lt;p&gt;We&apos;re trying to release 3.4 (rc2 should be out soon), but the development pace is a bit slower than a year before. We&apos;re basically almost 2 months late on what was our previous release rate. Not a big deal however.&lt;/p&gt;
&lt;p&gt;I&apos;ve started working on 3.5 slowly. It gonna get amazing new features too. :-)&lt;/p&gt;
&lt;h2&gt;Google Summer Of Code 2009&lt;/h2&gt;
&lt;p&gt;I&apos;ve mentored Mariusz Ceier on &lt;a href=&quot;http://xcb.freedesktop.org&quot;&gt;XCB&lt;/a&gt; GSoC. He worked on adding Xinput2 and XKB extensions. And he managed to do this. His work should be imported ASAP, the discussion has started on XCB maling list last week.&lt;/p&gt;
&lt;p&gt;In exchange, Google offered me (and to every mentor) an awful blue t-shirt! Thanks Google! :-P&lt;/p&gt;
</content:encoded></item><item><title>TODO list management</title><link>https://julien.danjou.info/blog/todo-list-management/</link><guid isPermaLink="true">https://julien.danjou.info/blog/todo-list-management/</guid><description>My fellow Debian developer Steve Kemp told us about his TODO list management.</description><pubDate>Fri, 10 Jul 2009 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;My fellow Debian developer &lt;a href=&quot;http://blog.steve.org.uk&quot;&gt;Steve Kemp&lt;/a&gt; told us about his &lt;a href=&quot;http://blog.steve.org.uk/why_do_you_keep_torturing_yourself_.html&quot;&gt;TODO list management&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While reading his post, I was constantly thinking &quot;been there, been there buddy&quot;. Yeah, I&apos;ve been.&lt;/p&gt;
&lt;p&gt;I had the same problem since months, impossibility to track the things I had to do, being computer related stuff or real life ones. The bad thing is that until you write them down, you keep them in mind, and that&apos;s exhausting. You know you have, let&apos;s say 5, things to do, but unless you write this 5 items down in a TODO list, you will keep thinking about it once in a while. And that&apos;s a real lost time.&lt;/p&gt;
&lt;p&gt;And that&apos;s totally inefficient: imagine you though &quot;it&apos;d be nice if I could buy a USB stick next time I buy some hardware&quot;. Well, unless you actually write this somewhere and have the habit to check the &quot;To Buy&quot; category of your TODO list, you&apos;re going to buy a replacement hard drive in a hurry some day, and forget about your USB stick.&lt;/p&gt;
&lt;p&gt;I think the good practice, which I really recommend to everyone, is to write down as soon as possible what you think you have to do. Don&apos;t write it on a small paper you will lose, write it in a TODO list, a paper or electronic one, whatever, but write it, and stop thinking about it. When you&apos;ll have time, you&apos;ll get your TODO list from your pocket and give a look at it, doing what you can do at that moment. Once in a while, you check that list.&lt;/p&gt;
&lt;p&gt;Personally, the tool I chose to handle my TODO list is a Palm Centro phone, which I got for only a hundred of euros. It runs the good old PalmOS, which basically know how to handle TODO list and plannings better than all phones I saw so far (and yes, probably better than your iPhone).&lt;/p&gt;
&lt;p&gt;My choice was based on the fact that I&apos;ve random ideas almost everywhere: that means while hacking, but also while walking in the street, while being in the train or while sleeping (yeah, already happened). And the only thing I always carry with me is my phone, in my pocket.&lt;/p&gt;
&lt;p&gt;However, Steve choice may be nice if you have Internet access on your phone, which I haven&apos;t since it&apos;s too expensive for what it is, in my opinion. :-)&lt;/p&gt;
</content:encoded></item><item><title>Upgrading to dovecot 1.2: hello Sieve!</title><link>https://julien.danjou.info/blog/upgrading-to-dovecot-1-2-hello-sieve/</link><guid isPermaLink="true">https://julien.danjou.info/blog/upgrading-to-dovecot-1-2-hello-sieve/</guid><description>Last year, I told you I wanted to use Sieve) to filter my mail. I did not switch, because of the lacking implementation of some Sieve features inside Dovecot, my preferred IMAP server.</description><pubDate>Thu, 09 Jul 2009 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Last year, I told you I wanted to use &lt;a href=&quot;http://en.wikipedia.org/wiki/Sieve_(mail_filtering_language)&quot;&gt;Sieve&lt;/a&gt; to filter my mail. I did not switch, because of the lacking implementation of some Sieve features inside &lt;a href=&quot;http://www.dovecot.org&quot;&gt;Dovecot&lt;/a&gt;, my preferred IMAP server.&lt;/p&gt;
&lt;p&gt;After that disapointement, I kept my 8 years old mail setup, being &lt;em&gt;fetchmail&lt;/em&gt; running on my workstation and throwing the mails in &lt;em&gt;procmail&lt;/em&gt;, then using &lt;em&gt;mutt&lt;/em&gt; locally to read the maildirs. But that&apos;s over.&lt;/p&gt;
&lt;p&gt;I got a laptop to replace my workstation. It was not possible to continue using such a mail setup, since my laptop can be offline, and so would be my mails.&lt;/p&gt;
&lt;p&gt;So I decided to upgrade Dovecot to 1.2. I used the &lt;em&gt;dovecot-1.2-work&lt;/em&gt; Subversion branch of our lovely Debian maintainers, and built a Debian package for Lenny. The upgrade from 1.1 was almost painless, since the configuration file did not change heavily.&lt;/p&gt;
&lt;p&gt;Then I started to write my little Sieve script. Sieve is a very nice language. Almost user friendly. So in 20 lines I rewrote all my procmail stuff, matching things like &lt;em&gt;List-Id&lt;/em&gt; with regex to put the mails automagically in the right folder. I reconfigured &lt;em&gt;mutt&lt;/em&gt; to use IMAP, and it works fine. I even reimported my old Maildir via IMAP using &lt;em&gt;mutt&lt;/em&gt; too.&lt;/p&gt;
&lt;p&gt;I am now a happy IMAP user.&lt;/p&gt;
&lt;p&gt;For people wondering why I wanted to switch away from &lt;em&gt;procmail&lt;/em&gt; to &lt;em&gt;Sieve&lt;/em&gt;: the reason is that Sieve script can be uploaded remotely via &lt;em&gt;managesieve&lt;/em&gt;. This means you do not need FTP/SSH/whatever access to put your script. You can, for example, use &lt;em&gt;connect-sieve&lt;/em&gt; or the Sieve plugin for Thunderbird/Icedove.&lt;/p&gt;
</content:encoded></item><item><title>Taking the other direction</title><link>https://julien.danjou.info/blog/taking-the-other-direction/</link><guid isPermaLink="true">https://julien.danjou.info/blog/taking-the-other-direction/</guid><description>I&apos;ve started to develop awesome more than 18 months ago, and somehow I feel it&apos;s time to stop a bit and think where we come from and where we are going to.</description><pubDate>Wed, 15 Apr 2009 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve started to develop &lt;a href=&quot;http://awesome.naquadah.org&quot;&gt;awesome&lt;/a&gt; more than 18 months ago, and somehow I feel it&apos;s time to stop a bit and think where we come from and where we are going to.&lt;/p&gt;
&lt;h2&gt;The motivation&lt;/h2&gt;
&lt;p&gt;I never though I&apos;d be written a window manager one day. That seems kinda stupid when you see how many window manager there&apos;s around.&lt;/p&gt;
&lt;p&gt;As many people, I&apos;ve tested and have been using tons of window manager: &lt;a href=&quot;http://www.windowmaker.info&quot;&gt;Window Maker&lt;/a&gt;, &lt;a href=&quot;http://www.fluxbox.org&quot;&gt;Fluxbox&lt;/a&gt;, etc.&lt;/p&gt;
&lt;p&gt;In August 2007, I was using &lt;a href=&quot;http://www.fvwm.org&quot;&gt;fvwm&lt;/a&gt; since 2004 and was quite happy with it. I used the famous &lt;a href=&quot;http://www.fvwm-crystal.org&quot;&gt;fvwm crystal&lt;/a&gt; as a configuration starter and then rewrote lots of stuff. Digging into &apos;&apos;fvwm&apos;&apos; configuration files was boring, and since I&apos;m lazy, I never really configured it to fit entirely my needs.&lt;/p&gt;
&lt;p&gt;The thing is that, in July 2007, my workstation died. I bought a new one based on the &lt;em&gt;amd64&lt;/em&gt; architecture. Too bad, with this new box, &lt;em&gt;fvwm&lt;/em&gt; decided that it will not longer runs and was segfaulting almost every time I logged in.&lt;/p&gt;
&lt;p&gt;I was &lt;strong&gt;really&lt;/strong&gt; upset. Another failure in the window manager world. So I decided to get the yearly ride of testing many window managers. I went on the no more developed stuff like the *boxes, ion3, etc… but well, I did not like them, there were not powerful enough, too bugged or upstream was insane.&lt;/p&gt;
&lt;p&gt;Then I found &lt;a href=&quot;http://www.xmonad.org&quot;&gt;xmonad&lt;/a&gt;. The Haskell configuration file format made my cry. I did not want to learn Haskell, it seemed too obfuscated to me. At that time it was even not packaged for &lt;a href=&quot;http://www.debian.org&quot;&gt;Debian&lt;/a&gt;, so I gave up. But I found&lt;/p&gt;
&lt;h2&gt;The jdwm&lt;/h2&gt;
&lt;p&gt;I just added a &apos;j&apos; in front of &lt;em&gt;dwm&lt;/em&gt; and started to hack it days and nights to add many feature I missed, like multi-head, etc… On 5th September 2007, I created a git repository to host my code.&lt;/p&gt;
&lt;h2&gt;That&apos;s gonna be… awesome.&lt;/h2&gt;
&lt;p&gt;Five days later, on 10th September, I finally found a name for my new pet: &lt;strong&gt;awesome&lt;/strong&gt;, borrowed from &lt;a href=&quot;http://en.wikipedia.org/wiki/Barney_Stinson&quot;&gt;Barney Stinson&lt;/a&gt; who heavily uses and abuses this word.&lt;/p&gt;
&lt;h3&gt;The 1.x branch&lt;/h3&gt;
&lt;p&gt;The first releases until December were noted 1.x. It was just a better &lt;em&gt;dwm&lt;/em&gt; with a simple flat configuration file.. The configuration file used &lt;a href=&quot;http://www.hyperrealm.com/libconfig/&quot;&gt;libconfig&lt;/a&gt;, but it was a very poor choice. And I was not able to put in into Debian because of &lt;a href=&quot;http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=441200&quot;&gt;name clash&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;The 2.x branch&lt;/h3&gt;
&lt;p&gt;The 2.x branch came in January 2008 with a brand new configuration file format based on &lt;a href=&quot;http://www.nongnu.org/confuse/&quot;&gt;libconfuse&lt;/a&gt;, which was a bit more powerful. Many concepts and features that have been added in this branch are still used in the current 3.x branch.&lt;/p&gt;
&lt;p&gt;At this time, between December 2007 and April 2008, the community was growing smoothly.&lt;/p&gt;
&lt;p&gt;But as I said, awesome 2 was based on a flat configuration file. That raised a problem very soon: users expectation were growing and the development team (me and a couple of regular contributors) was unable to cope with them.&lt;/p&gt;
&lt;p&gt;One of the event that started to change my mind was the support for titlebars.&lt;/p&gt;
&lt;p&gt;When I&apos;ve added titlebar support, it was minimal. It was on top of a window, with the window title. Dot. Then I&apos;ve started to add a lot of options, like the application icon drawing, the position (left, right, bottom) etc.&lt;/p&gt;
&lt;p&gt;And then users started to ask for more, like: &quot;add titlebar on windows only when the window is floating&quot;.&lt;/p&gt;
&lt;p&gt;That&apos;s ok, but that&apos;s complicated: that&apos;s again &lt;strong&gt;another&lt;/strong&gt; option to do some stuff conditionally. And then, why don&apos;t add titlebar on windows when ?&lt;/p&gt;
&lt;h3&gt;The 3.x branch&lt;/h3&gt;
&lt;h4&gt;Why&lt;/h4&gt;
&lt;p&gt;At that time, around April 2008, I&apos;d totally stopped development. I was trying to find a solution which was simple and powerful. But after 2 weeks of thinking, I was not able to find anything else than: use a real language for configuration.&lt;/p&gt;
&lt;p&gt;So, I&apos;ve started prototyping awesome 3 using &lt;a href=&quot;http://www.lua.org&quot;&gt;Lua&lt;/a&gt;. The choice was not obvious, and despite the problem &lt;em&gt;Lua&lt;/em&gt; might suffer, it&apos;s one of the easiest language to integrate into an existing application.&lt;/p&gt;
&lt;p&gt;But, let&apos;s go a little back: in January 2008, Arnaud Fontaine contacted me because he was interested to use &lt;em&gt;awesome&lt;/em&gt; as one of its school project. He decided to port &lt;em&gt;awesome&lt;/em&gt; from &lt;em&gt;Xlib&lt;/em&gt; to &lt;em&gt;&lt;a href=&quot;http://xcb.freedesktop.org&quot;&gt;XCB&lt;/a&gt;&lt;/em&gt;, a modern asynchronous X library.&lt;/p&gt;
&lt;p&gt;His work took some time, but in May 2008, Arnaud did finished to port git master version of &lt;em&gt;awesome&lt;/em&gt; to use &lt;em&gt;XCB&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Consequently, I decided to start a new major branch, using &lt;em&gt;XCB&lt;/em&gt; instead of &lt;em&gt;Xlib&lt;/em&gt; (no change for users in this regard) and &lt;em&gt;Lua&lt;/em&gt; instead of our previous flat configuration file format.&lt;/p&gt;
&lt;h4&gt;Development&lt;/h4&gt;
&lt;p&gt;It took me a while to get from here to there, but in September 2008, it was ready. We had a simple Lua API, and the XCB port was working perfectly.&lt;/p&gt;
&lt;p&gt;It took us some time to release and have something totally working, because we had to work on &lt;em&gt;XCB&lt;/em&gt; and contribute back to the project. It was really not ready to use by an application, but we did great work in this area and it&apos;s now really fine.&lt;/p&gt;
&lt;h4&gt;We&apos;re still here&lt;/h4&gt;
&lt;p&gt;Releases continue to happens, 3.1 around December 2008, and 3.2 around March 2009. 3.3 should be here in June.&lt;/p&gt;
&lt;p&gt;One of the drawback we had, is that we moved many stuff from C to Lua. Why? Because writing things in Lua is quicker and easier to maintain than C, and makes thing more configurable for the user.&lt;/p&gt;
&lt;p&gt;For example, the layout algorithm used to organize window were written in C until 3.2 came out. At that time, users had no choice than using a set of predefined layout to organize their windows.&lt;/p&gt;
&lt;p&gt;Starting with 3.2, if they have minimal knowledge about geometry, they can start writing a layout function organising windows on the screen.&lt;/p&gt;
&lt;p&gt;But this kind of API changes was a bit rough for users, since they had to port some part of their configuration file to the new API. The thing is that the project was still a &lt;em&gt;teenager&lt;/em&gt; at that time, not really knowing were it will go. But I&apos;m happy to announce that API breakage are more and more rare (so far only one minor between 3.2 and 3.3), and anyway always for the Good.&lt;/p&gt;
&lt;p&gt;But I admit that it built a bad reputation around &lt;em&gt;awesome 3.x&lt;/em&gt; during its first month of existence.&lt;/p&gt;
&lt;h2&gt;Future direction&lt;/h2&gt;
&lt;p&gt;I am currently working on 3.3 development. We have still many things to do. Time passing, we get more idea, and more users. And more users bring more ideas. We also have many more contributors, and some guys are even taking maintainer-ship of some code area.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;My post title is &quot;Taking the other direction&quot; because I feel this way.&lt;/p&gt;
&lt;p&gt;I&apos;ve got that feeling that some approaches in projects like GNOME are sometimes bad. Please don&apos;t misread me, I know we are not playing in the same yard.&lt;/p&gt;
&lt;p&gt;When adding a key shortcut for starting an application makes you dig into &lt;em&gt;gconf&lt;/em&gt;, I wonder how this is a win for the user.&lt;/p&gt;
&lt;p&gt;Well, it&apos;s probably a win for the end-user, but I surely am not one of them. And I don&apos;t intend to target them with my software, anyway.&lt;/p&gt;
&lt;p&gt;And now, when I hear things like GNOME 3.0 and the &quot;&lt;a href=&quot;http://live.gnome.org/GnomeShell%5D&quot;&gt;desktop shell&lt;/a&gt;&quot; approach, that makes me smile. Guys, it was time, but have luck. What I see from here, is that any desktop control interface is wrong somehow, and that there&apos;s no approach that can fulfill all users wishes.&lt;/p&gt;
&lt;p&gt;I think that we, the awesome development team (no pun intended) took the direction of building a frame-work window manager rather than a solution written in marble.&lt;/p&gt;
&lt;p&gt;We (partially) solved the issue of UI ergonomic by not writing one and allowing the user to write his own. I don&apos;t say that&apos;s easy to do for most of users, but it&apos;s doable.&lt;/p&gt;
&lt;p&gt;And I think it&apos;s worth it: I use window managers since I use Linux, around 1998. If something like &apos;&apos;awesome&apos;&apos; came 5 years ago, I&apos;d be using it so far, because you can write &lt;em&gt;Fluxbox&lt;/em&gt; or &lt;em&gt;WindowMaker&lt;/em&gt; using &lt;em&gt;awesome&lt;/em&gt; in a hundred of Lua code. And you can write your own version of it. And it starts in less than 3 seconds, supporting almost all standard desktop specification (ICCCM, EWMH, XDG, system tray, message notification, D-Bus, etc), whereas many of the window mangers do not.&lt;/p&gt;
&lt;p&gt;You can even write and play &lt;a href=&quot;http://awesome.naquadah.org/apidoc/modules/invaders.html&quot;&gt;space invaders&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Finally, I&apos;m happy about the the road we took so far, and hope we will continue into that direction. The rants I read about our project are not that big, compared to the kudos we received.&lt;/p&gt;
</content:encoded></item><item><title>OpenOffice is better as a pager than as a text processor</title><link>https://julien.danjou.info/blog/openoffice-better-as-a-pager/</link><guid isPermaLink="true">https://julien.danjou.info/blog/openoffice-better-as-a-pager/</guid><description>Since several month, awesome users have reported a bug with OpenOffice.org. When using OOo and clicking on a menu, or using the mouse wheel to read a document, the currently selected tag (desktop).</description><pubDate>Wed, 11 Feb 2009 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Since several month, &lt;a href=&quot;http://awesome.naquadah.org&quot;&gt;awesome&lt;/a&gt; users have reported a bug with &lt;a href=&quot;http://www.openoffice.org&quot;&gt;OpenOffice.org&lt;/a&gt;. When using OOo and clicking on a menu, or using the mouse wheel to read a document, the currently selected tag (desktop) will change automagically to another one.&lt;/p&gt;
&lt;p&gt;I&apos;ve digged into awesome and found that awesome received a _NET_CURRENT_DESKTOP request. As defined by &lt;a href=&quot;http://standards.freedesktop.org/wm-spec/wm-spec-latest.html#id2550663&quot;&gt;EWMH&lt;/a&gt;,&lt;br /&gt;
this kind of request are sent by a pager to change the active desktop.&lt;/p&gt;
&lt;p&gt;That was weird. Nobody is using a pager here. So, I just kicked my gdb out, attached it to OOo, breaking on &lt;em&gt;XSendEvent&lt;/em&gt; call. And I got it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Breakpoint 1, XSendEvent (dpy=0x1a00080, w=483, propagate=0, event_mask=1572864, event=0x7fff1fd70d70)
   at ../../src/SendEvent.c:46
(gdb) bt
#0  XSendEvent (dpy=0x1a00080, w=483, propagate=0, event_mask=1572864, event=0x7fff1fd70d70)
   at ../../src/SendEvent.c:46
#1  0x00007f8c0ab4193f in vcl_sal::WMAdaptor::switchToWorkArea ()
  from /usr/lib/openoffice/basis3.0/program/libvclplug_genlx.so
#2  0x00007f8c0aafdbd8 in X11SalFrame::Show ()
  from /usr/lib/openoffice/basis3.0/program/libvclplug_genlx.so
#3  0x00007f8c1378623c in Window::Show ()
  from /usr/lib/openoffice/program/../basis-link/program/libvcllx.so
#4  0x00007f8c13785f40 in Window::Show ()
  from /usr/lib/openoffice/program/../basis-link/program/libvcllx.so
#5  0x00007f8c1372cb54 in FloatingWindow::StartPopupMode ()
  from /usr/lib/openoffice/program/../basis-link/program/libvcllx.so
#6  0x00007f8c1373c877 in ?? () from /usr/lib/openoffice/program/../basis-link/program/libvcllx.so
#7  0x00007f8c1373ccf2 in ?? () from /usr/lib/openoffice/program/../basis-link/program/libvcllx.so
#8  0x00007f8c1373ce84 in ?? () from /usr/lib/openoffice/program/../basis-link/program/libvcllx.so
#9  0x00007f8c13795e7f in ?? () from /usr/lib/openoffice/program/../basis-link/program/libvcllx.so
#10 0x00007f8c13797e74 in ?? () from /usr/lib/openoffice/program/../basis-link/program/libvcllx.so
#11 0x00007f8c13796748 in ?? () from /usr/lib/openoffice/program/../basis-link/program/libvcllx.so
#12 0x00007f8c0aafe6f8 in X11SalFrame::HandleMouseEvent ()
  from /usr/lib/openoffice/basis3.0/program/libvclplug_genlx.so
#13 0x00007f8c0ab040c2 in X11SalFrame::Dispatch ()
  from /usr/lib/openoffice/basis3.0/program/libvclplug_genlx.so
#14 0x00007f8c0ab31625 in SalX11Display::Yield ()
  from /usr/lib/openoffice/basis3.0/program/libvclplug_genlx.so
#15 0x00007f8c0ab356f3 in ?? () from /usr/lib/openoffice/basis3.0/program/libvclplug_genlx.so
#16 0x00007f8c0ab2df1f in SalXLib::Yield () from /usr/lib/openoffice/basis3.0/program/libvclplug_genlx.so
#17 0x00007f8c135b050e in Application::Yield ()
  from /usr/lib/openoffice/program/../basis-link/program/libvcllx.so
#18 0x00007f8c135b0587 in Application::Execute ()
  from /usr/lib/openoffice/program/../basis-link/program/libvcllx.so
#19 0x00007f8c17517e80 in ?? () from /usr/lib/openoffice/program/../basis-link/program/libsofficeapp.so
#20 0x00007f8c135b4b24 in ?? () from /usr/lib/openoffice/program/../basis-link/program/libvcllx.so
#21 0x00007f8c135b4bc5 in SVMain () from /usr/lib/openoffice/program/../basis-link/program/libvcllx.so
#22 0x00007f8c1754ca6c in soffice_main ()
  from /usr/lib/openoffice/program/../basis-link/program/libsofficeapp.so
#23 0x000000000040105b in main ()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I started digging more into the code, and this is what I finally found in &lt;em&gt;salframe.cxx&lt;/em&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;        // #i45160# switch to desktop where a dialog with parent will appear
        if( mpParent &amp;amp;&amp;amp; mpParent-&amp;gt;m_nWorkArea != m_nWorkArea )
            GetDisplay()-&amp;gt;getWMAdaptor()-&amp;gt;switchToWorkArea(mpParent-&amp;gt;m_nWorkArea );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Beautiful! It even has a comment with a IssueZilla bug number. Let&apos;s go and see where it comes from.&lt;/p&gt;
&lt;p&gt;After 10 minutes of research to find that fucking IZ, I finally found the link to the &lt;a href=&quot;http://www.openoffice.org/issues/show_bug.cgi?id=45160&quot;&gt;issue #45160&lt;/a&gt;. The bug is IMHO not related to OOo but to a window manager doing poor job.&lt;/p&gt;
&lt;p&gt;I&apos;ve found that an awesome user already reported an bug… err, wait, I mean an issue as &lt;a href=&quot;http://www.openoffice.org/issues/show_bug.cgi?id=96684&quot;&gt;issue #96684&lt;/a&gt; (remember there&apos;s no bug in OOo, only issues) and I commented about it.&lt;/p&gt;
&lt;p&gt;It seems OOo developers have agreed to fix that bug eventually.&lt;/p&gt;
</content:encoded></item><item><title>startup-notification ported to XCB</title><link>https://julien.danjou.info/blog/startup-notification-ported-to-xcb/</link><guid isPermaLink="true">https://julien.danjou.info/blog/startup-notification-ported-to-xcb/</guid><description>Since Tuesday, I&apos;ve begun to work on XCB portage of the startup-notification library.</description><pubDate>Thu, 29 Jan 2009 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Since Tuesday, I&apos;ve begun to work on &lt;a href=&quot;http://xcb.freedesktop.org&quot;&gt;XCB&lt;/a&gt; portage of the &lt;a href=&quot;http://www.freedesktop.org/software/startup-notification/&quot;&gt;startup-notification&lt;/a&gt; library.&lt;/p&gt;
&lt;p&gt;I&apos;ve just completed the job, and &lt;a href=&quot;http://lists.freedesktop.org/archives/xdg/2009-January/010176.html&quot;&gt;send a bunch of patches to the XDG mailing list&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If the patches are merged, which I don&apos;t doubt, I&apos;ll be able to use this lib into &lt;a href=&quot;http://awesome.naquadah.org&quot;&gt;awesome&lt;/a&gt;, which would be nice step to the Freedesktop standard compliance I like to make.&lt;/p&gt;
</content:encoded></item><item><title>Rants about Lua</title><link>https://julien.danjou.info/blog/rants-about-lua/</link><guid isPermaLink="true">https://julien.danjou.info/blog/rants-about-lua/</guid><description>I&apos;ve started using Lua some months ago, while looking for a more powerful way to configure awesome. At this time, around March 2008, Lua seemed to be the best language to integrate inside the core.</description><pubDate>Tue, 30 Dec 2008 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I&apos;ve started using &lt;a href=&quot;http://www.lua.org&quot;&gt;Lua&lt;/a&gt; some months ago, while looking for a more powerful way to configure &lt;a href=&quot;http://awesome.naquadah.org&quot;&gt;awesome&lt;/a&gt;. At this time, around March 2008, Lua seemed to be the best language to integrate inside the core system of &lt;em&gt;awesome&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I still think that Lua was a good choice, but after 8 months, it shows some important drawbacks.&lt;/p&gt;
&lt;p&gt;I&apos;ll try to keep my explanation simple and to make you understand everything, even if you do not know Lua.&lt;/p&gt;
&lt;p&gt;I refer here to Lua version 5.1.&lt;/p&gt;
&lt;h2&gt;Design flaws&lt;/h2&gt;
&lt;h3&gt;Length operator&lt;/h3&gt;
&lt;p&gt;Lua has a length operator on its objects, known as &lt;code&gt;#&lt;/code&gt;. It can be used to get the size of various objects.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; return #&quot;lol&quot;
&amp;gt; 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This operator works for table, string, etc… It&apos;s possible to define this operator by setting a &lt;code&gt;__len&lt;/code&gt; meta-method on a userdata value.&lt;/p&gt;
&lt;p&gt;The problem is that you cannot redefine it on string or table objects, see:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; a = { &quot;hello&quot;, &quot;world&quot; }
&amp;gt; return #a
2
&amp;gt; setmetatable(a, { __len = function () return 18 end })
&amp;gt; return #a
2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Indeed, looking at the Lua core code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;      case OP_LEN: {
        const TValue *rb = RB(i);
        switch (ttype(rb)) {
          case LUA_TTABLE: {
            setnvalue(ra, cast_num(luaH_getn(hvalue(rb))));
            break;
          }
          case LUA_TSTRING: {
            setnvalue(ra, cast_num(tsvalue(rb)-&amp;gt;len));
            break;
          }
          default: {  /* try metamethod */
            Protect(
              if (!call_binTM(L, rb, luaO_nilobject, ra, TM_LEN))
                luaG_typeerror(L, rb, &quot;get length of&quot;);
            )
          }
        }
        continue;
      }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You clearly see that tables and strings always use the internal length operator, never the &lt;code&gt;__len&lt;/code&gt; meta-method.&lt;/p&gt;
&lt;p&gt;That&apos;s for me a design problem, which will cause more trouble. We&apos;ll see later.&lt;/p&gt;
&lt;h3&gt;index and newindex metamethods&lt;/h3&gt;
&lt;p&gt;Lua defines two useful meta-methods, which are &lt;code&gt;__index&lt;/code&gt; and &lt;code&gt;__newindex&lt;/code&gt;. Both can be set on a table or any other object. &lt;code&gt;__index&lt;/code&gt; will be called upon each read access to an undefined key on an object, and &lt;code&gt;__newindex&lt;/code&gt; upon each write access.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; a = {}
&amp;gt; -- function are not defined, this is just an example
&amp;gt; setmetatable(a, { __index = myindexfunction, __newindex = mynewindexfunction })
&amp;gt; a[1] = &quot;hello&quot; -- This will call __newindex metamethod
&amp;gt; return a[2] -- This will call __index metamethods
&amp;gt; return a[1] -- This will NOT call __index
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The last line does not call &lt;code&gt;__index&lt;/code&gt; meta-method because &lt;code&gt;a[1]&lt;/code&gt; does exists. This is a problem when you want to use table as object, because sometimes you want to monitor access to the table elements.&lt;/p&gt;
&lt;p&gt;This can be easily worked around using a proxy system: you don&apos;t store&lt;br /&gt;
things in the table you manipulate, but in another table.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; a = {}
&amp;gt; realtable = {}
&amp;gt; -- function are not defined, this is just an example:
&amp;gt; setmetatable(a, { __index = myindexfunction, __newindex = mynewindexfunction })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where meta-methods are something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function myindexfunction(table, key)
   return realtable[key]
end

function mynewindexfunction(table, key, value)
   realtable[key] = value
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This way, our &lt;code&gt;a&lt;/code&gt; table will always be empty, and &lt;code&gt;realtable&lt;/code&gt; will have the data. At every read or write access to &lt;code&gt;a&lt;/code&gt;, the meta-methods will be called. This is very convenient and widely used hack.&lt;/p&gt;
&lt;p&gt;But this has serious drawbacks: as we saw before, the length operator (&lt;code&gt;#&lt;/code&gt;) cannot be redefined on a table. That means &lt;code&gt;#a&lt;/code&gt; will always be &lt;code&gt;0&lt;/code&gt;, and you cannot get the table length anymore, except by defining another method or a special attribute.&lt;/p&gt;
&lt;p&gt;Also, Lua has several functions in the &lt;em&gt;table&lt;/em&gt; library that are used to manipulate table in a easy way. The problem is that standard functions like &lt;code&gt;table.insert&lt;/code&gt; or &lt;code&gt;table.remove&lt;/code&gt; do raw accesses to the table. Meaning that if you do &lt;code&gt;table.insert(a, 1, 1)&lt;/code&gt; it will insert the value 1 at the key 1 into &lt;em&gt;a&lt;/em&gt;, &lt;strong&gt;without&lt;/strong&gt; calling the &lt;code&gt;__newindex&lt;/code&gt; meta-methods, breaking all your beautiful object-oriented model.&lt;/p&gt;
&lt;p&gt;Another solution is to use a userdata object, like done in the Lua &lt;code&gt;newproxy&lt;/code&gt; function (which is under-documented).&lt;/p&gt;
&lt;p&gt;The problem is that it breaks all the other functions that are waiting for a table as argument, because they see a userdata, not a table. So this time &lt;code&gt;table.insert&lt;/code&gt; is now more usable, which somehow fixes the problem, but not in the right way IMHO. However, this allows to use the &lt;code&gt;__len&lt;/code&gt; meta-method.&lt;/p&gt;
&lt;h2&gt;Development model&lt;/h2&gt;
&lt;p&gt;The development model of Lua is, from my point of view, non-existent.&lt;/p&gt;
&lt;p&gt;There is no public version control system repository available, so there&apos;s no chance to really contribute to Lua. It seems only a defined set of people work on it and therefore, the development system is very closed to my eyes, in comparison of usual projects.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I still think Lua is a good choice, because it is very easy to integrate into any C program, and to expand to fulfill your needs. However, some bad design choices were made, and the poor and closed development model chosen does not allow to have a good overview of the future of Lua.&lt;/p&gt;
&lt;p&gt;This has been &lt;a href=&quot;http://lua-users.org/lists/lua-l/2008-06/msg00407.html&quot;&gt;well stated by the authors themselves&lt;/a&gt;.&lt;/p&gt;
</content:encoded></item><item><title>Security bug found in Imlib2</title><link>https://julien.danjou.info/blog/security-bug-found-in-imlib2/</link><guid isPermaLink="true">https://julien.danjou.info/blog/security-bug-found-in-imlib2/</guid><description>Yeah, I&apos;m the proud discover of CVE-2008-5187.</description><pubDate>Sat, 22 Nov 2008 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Yeah, I&apos;m the proud discover of &lt;a href=&quot;http://www.securityfocus.com/bid/32371&quot;&gt;CVE-2008-5187&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It&apos;s my first time, it does mean something to me. ;-)&lt;/p&gt;
</content:encoded></item><item><title>The eggtray problem</title><link>https://julien.danjou.info/blog/the-eggtray-problem/</link><guid isPermaLink="true">https://julien.danjou.info/blog/the-eggtray-problem/</guid><description>I still don&apos;t know why but many GTK+ applications use something called eggtrayicon. As far as I know, eggtrayicon.c is a file written in 2002 by Anders Carlsson which implements the Freedesktop.org sy</description><pubDate>Fri, 03 Oct 2008 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I still don&apos;t know why but many GTK+ applications use something called eggtrayicon. As far as I know, &lt;em&gt;eggtrayicon.c&lt;/em&gt; is a file written in 2002 by Anders Carlsson which implements the &lt;a href=&quot;http://standards.freedesktop.org/systemtray-spec/latest/&quot;&gt;Freedesktop.org system tray&lt;/a&gt; procotol for GTK+ applications.&lt;/p&gt;
&lt;p&gt;Problem is that this C file is used in dozens of programs and maybe more, and is a bit bugged. I&apos;ve already send patches for &lt;a href=&quot;http://www.nongnu.org/mailnotify/&quot;&gt;mail-notification&lt;/a&gt; and &lt;a href=&quot;http://audacious-media-player.org&quot;&gt;Audacious&lt;/a&gt;. &lt;a href=&quot;http://www.pidgin.im/&quot;&gt;pidgin&lt;/a&gt; is the first fixed implementation I found and works quite well. Many other applications are probably affected.&lt;/p&gt;
&lt;p&gt;That seems to me like a real problem. Multiple copy of bad code instead of using &lt;a href=&quot;http://library.gnome.org/devel/gtk/2.14/GtkStatusIcon.html&quot;&gt;native GTK+ system tray implementation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So please stop using this bad implementation…&lt;/p&gt;
</content:encoded></item><item><title>Unexpected VARMon new release</title><link>https://julien.danjou.info/blog/unexpected-varmon-new-release/</link><guid isPermaLink="true">https://julien.danjou.info/blog/unexpected-varmon-new-release/</guid><description>This has been 4 years since I released a new upstream release of VARMon, the DAC960 administration tool.</description><pubDate>Mon, 18 Aug 2008 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This has been 4 years since I released a new upstream release of &lt;a href=&quot;http://github.com/jd/varmon&quot;&gt;VARMon&lt;/a&gt;, the DAC960 administration tool.&lt;/p&gt;
&lt;p&gt;There was a bug first discovered in &lt;a href=&quot;http://bugs.debian.org/401236&quot;&gt;#401236&lt;/a&gt;. It has been fixed in Debian with an ugly fix, which did not work finally for a long time. Recently &lt;a href=&quot;http://bugs.debian.org/491505&quot;&gt;#491505&lt;/a&gt; got opened too, which was the same as the previous one. But this time I got access to hardware, thanks to Christoph! And I finally fixed the bug. I&apos;ve even be able to test the fixes I wrote years ago for all of the compilation warnings.&lt;/p&gt;
&lt;p&gt;That&apos;s a shame that the problem was caused by dead code from the previous upstream, and that I did not realize that sooner. Kids, do not let dead debug code in your program at home.&lt;/p&gt;
&lt;p&gt;So I&apos;ve finally been able to release a new 1.2.1 version which maybe the last release for the next decade! ;-)&lt;/p&gt;
</content:encoded></item><item><title>ATL1E support in Linux 2.6.26-1</title><link>https://julien.danjou.info/blog/atl1e-support-in-2-6-26-1/</link><guid isPermaLink="true">https://julien.danjou.info/blog/atl1e-support-in-2-6-26-1/</guid><description>Ben Armstrong opened an ITP for the ATL1E NIC driver, which is found on some Asus EeePC laptops. So, as suggested by Maximilian Attems, I provided a clean patch for this driver, made from a.</description><pubDate>Thu, 31 Jul 2008 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Ben Armstrong opened an &lt;a href=&quot;http://bugs.debian.org/492029&quot;&gt;ITP for the ATL1E NIC driver&lt;/a&gt;, which is found on some Asus EeePC laptops. So, as suggested by Maximilian Attems, &lt;a href=&quot;http://lists.debian.org/debian-kernel/2008/07/msg00638.html&quot;&gt;I provided a clean patch for this driver&lt;/a&gt;, made from a cherry-pick from the linux-netdev 2.6.27 tree. It has been committed into the 2.6.26-1 Debian kernel, which will be furnished with Lenny.&lt;/p&gt;
&lt;p&gt;What&apos;s fun, is that in the mean time, I got a new computer at work. Wait, it&apos;s not fun yet. Because what I did not know is that it&apos;s made of an &lt;a href=&quot;http://asus.com/products.aspxl1=3&amp;amp;l2=11&amp;amp;l3=709&amp;amp;l4=0&amp;amp;model=2164&amp;amp;modelmenu=1&quot;&gt;Asus P5Q motherboard&lt;/a&gt; which runs a NIC needing the ATL1E driver (and now you see why it&apos;s fun).&lt;/p&gt;
&lt;p&gt;So I&apos;ve just upgraded to 2.6.26-1-amd64 and I&apos;m glad that my own work is useful to me (and will be probably be to others as well). :-)&lt;/p&gt;
</content:encoded></item><item><title>EWMH and XRandR</title><link>https://julien.danjou.info/blog/ewmh-and-xrandr/</link><guid isPermaLink="true">https://julien.danjou.info/blog/ewmh-and-xrandr/</guid><description>Today I decided to add some EWMH support to awesome. It now supports a bunch of this extensions quite nicely.</description><pubDate>Thu, 27 Dec 2007 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Today I decided to add some &lt;a href=&quot;http://en.wikipedia.org/wiki/EWMH&quot;&gt;EWMH&lt;/a&gt; support to &lt;a href=&quot;http://awesome.naquadah.org&quot;&gt;awesome&lt;/a&gt;. It now supports a bunch of this extensions quite nicely.&lt;/p&gt;
&lt;p&gt;However, while reading the &lt;a href=&quot;http://standards.freedesktop.org/wm-spec/wm-spec-1.4.html&quot;&gt;spec&lt;/a&gt; and writing the code, it appears that this forces a window manager to behave in only one way: have a poor desktop support, and no multi-head/XRandR/Xinerama support at all.&lt;/p&gt;
&lt;p&gt;The main caveats are that in Xinerama/XRandR mode, you&apos;ll have only one root window. And the root window is where you must store the NET_WM X properties… So you cannot handle screens in a independant way like &lt;em&gt;awesome&lt;/em&gt; does. That&apos;s really a shame.&lt;/p&gt;
&lt;p&gt;There&apos;s also a big problem for window managers like &lt;em&gt;awesome&lt;/em&gt; which are happy to draw several desktops at the same time. There&apos;s no support for stuff like that.&lt;/p&gt;
&lt;p&gt;So far, I think EWMH is nice but is really too narrow-minded for software and people who want to think window management in a different way.&lt;/p&gt;
</content:encoded></item><item><title>Kicking out Web spammers with DNSBL</title><link>https://julien.danjou.info/blog/kicking-out-web-spammers-with-dnsbl/</link><guid isPermaLink="true">https://julien.danjou.info/blog/kicking-out-web-spammers-with-dnsbl/</guid><description>Every project has its story. Every war has its winner, and its casualties. They were 20 millions men, fighting for their freedom.  And you&apos;ll never know their story.  Because during last week, I was l</description><pubDate>Mon, 15 Jan 2007 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Every project has its story. Every war has its winner, and its casualties. They were 20 millions men, fighting for their freedom.&lt;/p&gt;
&lt;p&gt;And you&apos;ll never know their story.&lt;/p&gt;
&lt;p&gt;Because during last week, I was looking why my Web server was so heavily loaded. And I discovered that my blog was attacked by spammers trying to post comments. They were stopped by a great plug-in named &lt;em&gt;spamplemousse&lt;/em&gt;, which use spam keywords and DNSBL to drop spam comments. However, this plug-in is written in PHP, like the rest of my blog, so it loads Apache and MySQL in a way that is no more acceptable: the page have still to be rendered for this !@#$ spammers.&lt;/p&gt;
&lt;p&gt;Consequently, I decided to write a Apache 2.x module which will just drop a &lt;em&gt;403 Forbidden&lt;/em&gt; error page in the spammers&apos; head using DNSBL servers. Here it is, and it is called &lt;a href=&quot;https://github.com/jd/mod_defensible&quot;&gt;mod_defensible&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&apos;m using it since 3 days now, and I got some pretty interesting result and less load on my Web server, so &lt;em&gt;c&apos;est tout bon&lt;/em&gt;.&lt;/p&gt;
</content:encoded></item></channel></rss>