<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xml:base="https://tjaddison.com/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>tjaddison.com</title>
    <link>https://tjaddison.com/</link>
    <atom:link href="https://tjaddison.com/rss.xml" rel="self" type="application/rss+xml" />
    <description>Engineering Management, SQL, and a dash of front-end from NYC.</description>
    <language>en</language>
    <item>
      <title>Half a year to complete the migration to Eleventy. What will I do with the rest of it?</title>
      <link>https://tjaddison.com/blog/2023/04/half-a-year-to-complete-the-migration-to-eleventy-what-will-i-do-with-the-rest-of-it/</link>
      <description>&lt;p&gt;After six months this blog is now running on Eleventy instead of Gatsby. In my &lt;a href=&quot;https://tjaddison.com/blog/2022/10/the-next-time-you-see-me-i-will-be-eleventy/&quot;&gt;previous post&lt;/a&gt; I talked about why I wanted to make the switch. Today I&#39;ll talk briefly about how I found the migration, whether the result is what I hoped for, and make a few predictions about the future of the site.&lt;/p&gt;
&lt;h2 id=&quot;the-upgrade-process&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2023/04/half-a-year-to-complete-the-migration-to-eleventy-what-will-i-do-with-the-rest-of-it/#the-upgrade-process&quot;&gt;The upgrade process&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Don&#39;t be fooled by the six month timing between posts. I&#39;d suggest that with focus the migration could have been done in a long day or a weekend.&lt;/p&gt;
&lt;p&gt;I&#39;d like to say the delta between that long weekend and six months was purely down to limited time, but I know I spent a lot of time trying to understand how Eleventy worked and why certain decisions had been made.&lt;/p&gt;
&lt;p&gt;Towards the end of the migration I stumbled on a few interesting blogs that helped me understand how people were using Eleventy in the wild - I never found the official docs to be helpful in this respect, and would suggest anyone looking to grok Eleventy starts by reading as broadly on the topic (through the eyes of others) as possible.&lt;/p&gt;
&lt;p&gt;Coming from Gatsby - a very much &#39;batteries included&#39; framework which is supported by a strong ecosystem of documentation - at times Eleventy felt like I wasn&#39;t smart enough to figure out what was clearly an obvious thing. The amount of time I wondered about what was the idiomatic way to put images in my posts left me convinced I&#39;d missed a tutorial somewhere (I&#39;m pretty sure I haven&#39;t). The learning curve felt needlessly high in many places, with the documentation gleefully presenting all the features but little guidance on when you might want to use or avoid them.&lt;/p&gt;
&lt;h2 id=&quot;the-results&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2023/04/half-a-year-to-complete-the-migration-to-eleventy-what-will-i-do-with-the-rest-of-it/#the-results&quot;&gt;The results&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The build process is faster - both locally and on Netlify, with builds on a cold cache dramatically faster. The client-side bundle has been eliminated. There is no more GraphQL involved. The number of referenced development packages (and thus &lt;code&gt;npm install&lt;/code&gt; performance) is greatly reduced.&lt;/p&gt;
&lt;p&gt;Navigating between pages doesn&#39;t feel as snappy as before (Gatsby pre-fetched and performed client-side navigation), which is a tradeoff I knew I&#39;d make in exchange for eliminating the client-side bundle. I haven&#39;t noticed any other changes, and the &lt;a href=&quot;https://web.dev/vitals/&quot;&gt;core web vitals&lt;/a&gt; agree.&lt;/p&gt;
&lt;p&gt;Happy with the current status of the project and site.&lt;/p&gt;
&lt;p&gt;I&#39;ll caveat the above by adding that I have migrated but not authored anything yet nor had to go through any upgrades. On top of that, I&#39;ve set myself a &#39;no tinkering until the first blog post is published&#39; rule which means that I can&#39;t even speak to the editing experience.&lt;/p&gt;
&lt;h2 id=&quot;predictions-for-the-future&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2023/04/half-a-year-to-complete-the-migration-to-eleventy-what-will-i-do-with-the-rest-of-it/#predictions-for-the-future&quot;&gt;Predictions for the future&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Offered with probabilities I&#39;ve pulled out of thin air, and something I can come back and examine in the future.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;100% chance I&#39;ll publish at least one post on the Eleventy blog in the next month (as I write this draft, I feel supremely confident)&lt;/li&gt;
&lt;li&gt;75% chance I&#39;ll complete at least two items from my &#39;do soon&#39; checklist in the next three months&lt;/li&gt;
&lt;li&gt;50% chance I&#39;ll complete at least four of them in the next three months&lt;/li&gt;
&lt;li&gt;10% chance I&#39;ll complete all of them in the next three months&lt;/li&gt;
&lt;li&gt;75% chance I&#39;ll publish at least two posts in the next six months&lt;/li&gt;
&lt;li&gt;75% chance I&#39;ll do something from the &#39;do later&#39; checklist in the next six months&lt;/li&gt;
&lt;li&gt;50% chance I&#39;ll do a quarter of them in the next six months&lt;/li&gt;
&lt;li&gt;25% chance I&#39;ll publish at least six posts in the next twelve months&lt;/li&gt;
&lt;li&gt;5% chance I&#39;ll spend time searching for an alternative static site generator in the next twelve months, based on an observed limitation/friction with the setup&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &#39;do soon&#39; and &#39;do later&#39; checklists are in the project&#39;s readme file - here&#39;s the &lt;a href=&quot;https://github.com/taddison/personal-site/blob/2897cc24e89d7dd646db53520bba1b51d54cd446/README.md&quot;&gt;current commit on GitHub&lt;/a&gt; if you&#39;d like to see what&#39;s there.&lt;/p&gt;
</description>
      <pubDate>Sat, 22 Apr 2023 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2023/04/half-a-year-to-complete-the-migration-to-eleventy-what-will-i-do-with-the-rest-of-it/</guid>
    </item>
    <item>
      <title>The next time you see me I will be Eleventy</title>
      <link>https://tjaddison.com/blog/2022/10/the-next-time-you-see-me-i-will-be-eleventy/</link>
      <description>&lt;p&gt;After slowly completing a series of explorations into how I can migrate from &lt;a href=&quot;https://www.gatsbyjs.com/&quot;&gt;Gatsby&lt;/a&gt; to &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt;, I&#39;ve decided to draw a line in the sand and say this will be my last post until it&#39;s done.&lt;/p&gt;
&lt;p&gt;The rest of this post will cover why I decided to switch away from Gatsby, what I&#39;m hoping to achieve with Eleventy, and thoughts on why I&#39;ve struggled to make the switch.&lt;/p&gt;
&lt;h2 id=&quot;history&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/10/the-next-time-you-see-me-i-will-be-eleventy/#history&quot;&gt;History&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This blog was originally built with &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt;. At the time I knew nothing about Ruby or &lt;a href=&quot;https://shopify.github.io/liquid/&quot;&gt;Liquid templates&lt;/a&gt; (largely unchanged today), and I made little effort to learn about the ecosystem. I was mostly happy with Hugo, though did have a pretty rough time when I tried to get my feet wet with Ruby - I like to think it was because running Ruby on Windows was a pain at the time, but more honestly I wasn&#39;t invested in learning that language.&lt;/p&gt;
&lt;p&gt;A few years later I was writing React recreationally and came across Gatsby. Their zero-touch image optimisation spoke to a problem I&#39;d started noticing with my site (where I did nothing to optimise images). That, plus the ability to work with a Static Site Generator (SSG) in a language I was comfortable with (JavaScript) convinced me to trial a migration, and I remember it being relatively painless to see demonstrable performance gains for my site. I migrated.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I wrote about my motivations to move in the post &lt;a href=&quot;https://tjaddison.com/blog/2019/09/migrating-from-jekyll-to-gatsby/&quot;&gt;migrating from Jekyll to Gatsby&lt;/a&gt;, which includes criteria I applied when selecting Gatsby.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I was happy with this setup for a while, though did find the hit &amp;amp; miss plugin quality and GraphQL &lt;em&gt;everywhere&lt;/em&gt; (even places it had no business being) were detractors from the experience.&lt;/p&gt;
&lt;h2 id=&quot;build-timeouts-and-upgrades&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/10/the-next-time-you-see-me-i-will-be-eleventy/#build-timeouts-and-upgrades&quot;&gt;Build Timeouts and Upgrades&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At some point in late 2020 my &lt;a href=&quot;https://www.netlify.com/&quot;&gt;Netlify&lt;/a&gt; builds started to time out (getting up to twenty minutes on the free tier before being terminated). Image processing accounted for ninety percent of this, which was surprising given the site had around a hundred images. Luckily Netlify was emitting warnings that there was a Gatsby cache plugin available that allowed assets to be shared between builds.&lt;/p&gt;
&lt;p&gt;This experience planted the seed to start looking elsewhere, a twenty minute build time for a site as small as mine was unreasonable.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I&#39;m not without blame - I&#39;ve done nothing to ensure the source images are well-sized and compressed, which I&#39;m certain would cut down on build times regardless of the image processing pipeline I use.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The experience that committed me to move was attempting the Gatsby v3 to v4 major upgrade. A modest collection of Gatsby plugins caused me to hit a number of issues with breaking changes on the plugins, and one edge case that caused builds to never complete (which was later fixed, but this was an awful experience for a core plugin that handled images).&lt;/p&gt;
&lt;p&gt;I never completed the major upgrade, and my site is currently stuck at Gatsby 3.9.1. I&#39;m sure some issues I experienced are fixed by now, but I&#39;m convinced the only future this ecosystem has to offer me is more pain with dependency upgrades.&lt;/p&gt;
&lt;h2 id=&quot;lessons-learned-and-looking-ahead&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/10/the-next-time-you-see-me-i-will-be-eleventy/#lessons-learned-and-looking-ahead&quot;&gt;Lessons Learned and Looking Ahead&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Reviewing the criteria that I used when deciding on the last migration, the only thing Gatsby failed to deliver on was &lt;em&gt;low/zero ongoing cost&lt;/em&gt;. A new requirement came from a more nuanced view of what &lt;em&gt;Fast&lt;/em&gt; means to me, encouraging me to eliminate the giant bundles I was shipping with the site (a truly static site).&lt;/p&gt;
&lt;p&gt;The other requirement should be a simple set of dependencies that are easy to extend. JavaScript is an incredibly capable language, and so many plugins and their deep tangle of dependencies are unnecessary.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Dependencies and the software supply chain are something I&#39;ve been spending a lot of time wondering about with a view to wondering how durable a given software or tool is going to be. I covered it briefly in &lt;a href=&quot;https://tjaddison.com/blog/2022/03/writing-a-simple-pocket-app-in-nodejs-with-no-dependencies/&quot;&gt;a pocket app with no dependencies&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;After initially considering and rejecting Eleventy due to the requirement of learning a new template language (Liquid or &lt;a href=&quot;https://mozilla.github.io/nunjucks/templating.html&quot;&gt;Nunjucks&lt;/a&gt;), I finally came back to it after accepting a learning curve was reasonable, and in the case of Nunjucks the templating language was &lt;a href=&quot;https://npmtrends.com/liquidjs-vs-nunjucks&quot;&gt;popular&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The other candidate I spent a long time considering was &lt;a href=&quot;https://nextjs.org/&quot;&gt;NextJS&lt;/a&gt;. After a few prototypes it was rejected as needlessly complicated for my needs, and categorically failed on the dependencies test.&lt;/p&gt;
&lt;h2 id=&quot;not-migrating&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/10/the-next-time-you-see-me-i-will-be-eleventy/#not-migrating&quot;&gt;Not Migrating&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As of early 2022 I&#39;d decided there was no future in Gatsby, and that Eleventy was the right answer. And it is now October 2022 and I&#39;m writing a post to assist in motivating me to &lt;em&gt;get it done&lt;/em&gt;. What&#39;s caused the delay?&lt;/p&gt;
&lt;p&gt;A big part of the delay is that life has been happening, as it is wont to do.&lt;/p&gt;
&lt;p&gt;The other part was working on the blog became an energy drain - it used to be something I enjoyed extending and tweaking, and now every interaction with the setup makes me wish I was using literally anything else.&lt;/p&gt;
&lt;p&gt;To combat this I tried to make forward progress with proof of concepts - things like ensuring I could get images working. These explorations produced the post &lt;a href=&quot;https://tjaddison.com/blog/2022/08/processing-images-linked-from-frontmatter-with-eleventy-img-to-use-in-meta-tags/&quot;&gt;processing images linked from frontmatter with Eleventy&lt;/a&gt; but didn&#39;t get the blog any closer to migrated. I&#39;m convinced this was mostly a procrastination tactic, and I could have learned everything I needed experimenting in a branch or fork of the site.&lt;/p&gt;
&lt;p&gt;And so that&#39;s what I&#39;m going to do now.&lt;/p&gt;
&lt;p&gt;This post is the swan song of the Gatsby v3 blog. The next post will be coming from the Eleventy edition.&lt;/p&gt;
</description>
      <pubDate>Thu, 13 Oct 2022 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2022/10/the-next-time-you-see-me-i-will-be-eleventy/</guid>
    </item>
    <item>
      <title>Processing images linked from frontmatter with eleventy-img to use in meta tags</title>
      <link>https://tjaddison.com/blog/2022/08/processing-images-linked-from-frontmatter-with-eleventy-img-to-use-in-meta-tags/</link>
      <description>&lt;p&gt;Given a markdown file (&lt;code&gt;/blog/hello-world/index.md&lt;/code&gt;) with frontmatter that looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;shareimage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;small.png&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;How can we process that image with &lt;a href=&quot;https://www.11ty.dev/docs/plugins/image/&quot;&gt;eleventy-img plugin&lt;/a&gt; and render a meta tag:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- note the generated filename is a content hash-width combination --&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;property&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;og:image&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/blog/hello-world/xfO_genLg4-600.png&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This post will cover the steps needed, and assumes a basic familiarity with &lt;a href=&quot;https://www.11ty.dev/docs/&quot;&gt;Eleventy&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Despite tonnes of great documentation of the various features of Eleventy and its plugins, the lack of an example that showed processing images via frontmatter left me struggling with this as a newcomer to the ecosystem.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;setup&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/08/processing-images-linked-from-frontmatter-with-eleventy-img-to-use-in-meta-tags/#setup&quot;&gt;Setup&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In addition to &lt;code&gt;@11ty/eleventy&lt;/code&gt; you&#39;ll need &lt;code&gt;@11ty/eleventy-img&lt;/code&gt;. The code below assumes you&#39;re using &lt;a href=&quot;https://www.11ty.dev/docs/languages/nunjucks/&quot;&gt;Nunjucks&lt;/a&gt; as the template language. Finally, we&#39;ve got the following file structure (note the images are in the same folder as the markdown file):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/blog
  /hello-world
    index.md
    giant.png
    small.png
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;processing-the-image&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/08/processing-images-linked-from-frontmatter-with-eleventy-img-to-use-in-meta-tags/#processing-the-image&quot;&gt;Processing the image&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To copy the image to our output folder (along with resizing and any other processing we want to do) we&#39;ll be creating a &lt;a href=&quot;https://www.11ty.dev/docs/shortcodes/&quot;&gt;shortcode&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// .eleventy.js&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; Image &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;@11ty/eleventy-img&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;shareImageShortcode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// src might be small.png - taken from frontmatter&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; url &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;page&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// url might be /blog/hello-world/&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; imageSrc &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;.&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; src&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; metadata &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;imageSrc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;widths&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;600&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;formats&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;jpeg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;urlPath&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;outputDir&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;./_site/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;url&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;jpeg&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// data.url might be /blog/hello-world/xfO_genLg4-600.jpeg&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// note the filename is a content hash-width combination&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;eleventyConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addNunjucksAsyncShortcode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&quot;shareImageUri&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    shareImageShortcode&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;markdownTemplateEngine&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;njk&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code above is making a few assumptions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We are creating a jpeg image (broadest support - we can&#39;t use multiple sources)&lt;/li&gt;
&lt;li&gt;We&#39;re making that image 600 pixels wide (we can&#39;t use a &lt;code&gt;srcset&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The output directory is &lt;code&gt;_site&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;The contents of &lt;code&gt;this.page&lt;/code&gt; are &lt;a href=&quot;https://www.11ty.dev/docs/data-eleventy-supplied/#page-variable-contents&quot;&gt;well documented&lt;/a&gt; and are available in all shortcodes&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The shortcode we&#39;ve created is &lt;code&gt;shareImageUri&lt;/code&gt; which we can now use in a template.&lt;/p&gt;
&lt;h2 id=&quot;using-the-shortcode-in-a-template&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/08/processing-images-linked-from-frontmatter-with-eleventy-img-to-use-in-meta-tags/#using-the-shortcode-in-a-template&quot;&gt;Using the shortcode in a template&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We can use the shortcode in a template (which will need to be one that can render into the final &lt;code&gt;head&lt;/code&gt; tag) like this:&lt;/p&gt;
&lt;pre class=&quot;language-nunjucks&quot;&gt;&lt;code class=&quot;language-nunjucks&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;!&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;part&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;of&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;njk&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;head&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;shareimage&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;meta&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;property&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;og:image&quot;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;{% shareImageUri shareimage %}&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;endif&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;head&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because of the &lt;a href=&quot;https://www.11ty.dev/docs/data-cascade/&quot;&gt;data cascade&lt;/a&gt; the frontmatter value &lt;code&gt;shareimage&lt;/code&gt; will be available. If you want a fallback image you can use an &lt;code&gt;{% else %}&lt;/code&gt; block to render a hardcoded alternative. You&#39;ll probably want to put that somewhere in the root of your site (e.g. &lt;code&gt;/img&lt;/code&gt;) rather than generating the same placeholder in every post&#39;s folder (which using the shortcode we created would do).&lt;/p&gt;
&lt;p&gt;And that&#39;s all there is to it!&lt;/p&gt;
&lt;h2 id=&quot;what-did-i-miss&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/08/processing-images-linked-from-frontmatter-with-eleventy-img-to-use-in-meta-tags/#what-did-i-miss&quot;&gt;What did I miss&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As a total newbie to Eleventy I&#39;m sure I&#39;ve missed some obvious stuff. If you can point me in the right direction on any of the below please mail me &lt;em&gt;eleventy-feedback&lt;/em&gt; at this blog&#39;s domain.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rather than hardcoding &lt;code&gt;_site&lt;/code&gt; is it accessible somewhere? I can see that the &lt;code&gt;outputPath&lt;/code&gt; starts with that but I&#39;d need to assume the output path was a single folder only.&lt;/li&gt;
&lt;li&gt;This all seemed fairly simple &lt;em&gt;in hindsight&lt;/em&gt;, and the documentation has all the bits needed to get it working. Is there an image tutorial I missed that explains this, or I am just spoiled coming from a world where Gatsby did a pretty good job at processing everything as an image?&lt;/li&gt;
&lt;li&gt;Working with local images using relative file paths was a little tricky both here and with rendering images in markdown posts (another post!) - is there a go-to tutorial/document/starter that covers this? Some of my path manipulation code feels like it could have been easier?&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Wed, 31 Aug 2022 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2022/08/processing-images-linked-from-frontmatter-with-eleventy-img-to-use-in-meta-tags/</guid>
    </item>
    <item>
      <title>Migrating my digital garden from WikiLens to Foam, and taking it private</title>
      <link>https://tjaddison.com/blog/2022/07/migrating-my-digital-garden-from-wikilens-to-foam-and-taking-it-private/</link>
      <description>&lt;p&gt;I&#39;ve &lt;a href=&quot;https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/&quot;&gt;previously talked about how much I loved&lt;/a&gt; the workflow that &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=vsls-contrib.gistfs&quot;&gt;GistPad&lt;/a&gt; (and more recently &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=lostintangent.wikilens&quot;&gt;WikiLens&lt;/a&gt;) enables for curating a digital garden. With the addition of &lt;a href=&quot;https://github.com/github/dev&quot;&gt;github.dev&lt;/a&gt; browsing and editing are now possible from a browser - something I called out as missing two years ago. Given the slickness of the setup, what&#39;s prompted a change? In short: data gravity.&lt;/p&gt;
&lt;h2 id=&quot;too-many-gardens&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/07/migrating-my-digital-garden-from-wikilens-to-foam-and-taking-it-private/#too-many-gardens&quot;&gt;Too many gardens&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Having multiple digital gardens has long been a pain point - specifically as the digital garden I curate with my family grows, there are more instances when daily notes straddle the public and the private. In some cases the hee-hawing over whether to write one note in each, a split note, or come up with some interesting cross-reference system has been distraction enough to not write anything down at all. &lt;a href=&quot;https://notes.andymatuschak.org/z21cgR9K3UcQ5a7yPsj2RUim3oM2TzdBByZu&quot;&gt;Working with the garage door up&lt;/a&gt; comes with some hidden costs it seems.&lt;/p&gt;
&lt;p&gt;I&#39;ll still have blog posts as a very public outlet, and in the future consider exploring ways to mark notes in a private garden as public. More on which in a moment.&lt;/p&gt;
&lt;h2 id=&quot;not-enough-gardening-tools&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/07/migrating-my-digital-garden-from-wikilens-to-foam-and-taking-it-private/#not-enough-gardening-tools&quot;&gt;Not enough... gardening tools?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The metaphor has got away from me a little, I&#39;ll admit.&lt;/p&gt;
&lt;p&gt;When collapsing the two digital gardens together I decided to take another look at how &lt;a href=&quot;https://github.com/foambubble/foam&quot;&gt;Foam&lt;/a&gt; had come along (after dismissing it as a little too early two years ago). I was pleased to see it delivers on its vision of providing &#39;just-enough&#39; tooling, but no more than that. A couple of items I was &lt;em&gt;extremely&lt;/em&gt; happy to see were templates, nested tags, and frontmatter support. Coupled with a vibrant community and a low switching cost, I decided to make the move.&lt;/p&gt;
&lt;h3 id=&quot;migrating&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/07/migrating-my-digital-garden-from-wikilens-to-foam-and-taking-it-private/#migrating&quot;&gt;Migrating&lt;/a&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;I migrated from a wiki that worked with &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=lostintangent.wikilens&quot;&gt;WikiLens&lt;/a&gt;. These steps may work for other markdown-based wikis, but I&#39;ve not tried them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Start by modifying the &lt;code&gt;.vscode/extensions.json&lt;/code&gt; file to recommend Foam rather than the extension you were using:&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;{&lt;br /&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; &quot;recommendations&quot;: [&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;    &quot;lostintangent.wikilens&quot;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;    &quot;foam.foam-vscode&quot;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; ]&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create an empty &lt;code&gt;.vscode/foam.json&lt;/code&gt; file to tell the Foam extension that this is a Foam workspace:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To get the daily page working I updated the &lt;code&gt;.vscode/settings.json&lt;/code&gt; to include the following:&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;{&lt;br /&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;  &quot;foam.openDailyNote.directory&quot;: &quot;Daily/&quot;,&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;  &quot;foam.openDailyNote.titleFormat&quot;: &quot;dddd, mmmm d, yyyy&quot;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This uses the &lt;a href=&quot;https://github.com/felixge/node-dateformat#mask-options&quot;&gt;fulldate format&lt;/a&gt; and defaults to creating the page in the &lt;code&gt;Daily&lt;/code&gt; folder. The default filename is still &lt;code&gt;yyyy-MM-dd.md&lt;/code&gt;, though that can also be customised by a setting.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I strongly encourage you to read the &lt;a href=&quot;https://foambubble.github.io/foam/#getting-started&quot;&gt;Foam documentation&lt;/a&gt;, and clone the sample to see all the features in action. The migration steps here only cover a subset of the capabilities of Foam.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;differences&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/07/migrating-my-digital-garden-from-wikilens-to-foam-and-taking-it-private/#differences&quot;&gt;Differences&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;One of the only differences I&#39;ve noticed is that &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=lostintangent.wikilens&quot;&gt;WikiLens&lt;/a&gt; uses the page title for links (as defined by the H1 in the document), whereas &lt;a href=&quot;https://github.com/foambubble/foam&quot;&gt;Foam&lt;/a&gt; uses the page name (based on the filename). As an example, links to daily pages required updates for their links:&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; Last time I worked on this (see [[August 18, 2020]])...&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; Last time I worked on this (see [[2020-08-18]])...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;early-thoughts&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/07/migrating-my-digital-garden-from-wikilens-to-foam-and-taking-it-private/#early-thoughts&quot;&gt;Early thoughts&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Beyond the fact I never have to think about which digital garden to put a daily note in (hooray!), not much has really changed. This is a great result, and further confirms my suspicion that markdown based note-taking is likely to be durable all the way through to &lt;a href=&quot;https://longnow.org/&quot;&gt;the long now&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One thing I&#39;ve found surprising is how little content I&#39;ve been able to organically discover that discusses Foam (how people are using it, extending it, migrating to it). Part of this could be the name (&lt;code&gt;Foam&lt;/code&gt; and &lt;code&gt;foambubble&lt;/code&gt; are challenging search terms to get lost amongst), though it could also be the community is not publishing this content and it&#39;s all in Discord/GitHub issues/Twitter.&lt;/p&gt;
&lt;h3 id=&quot;what-next&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/07/migrating-my-digital-garden-from-wikilens-to-foam-and-taking-it-private/#what-next&quot;&gt;What next?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For now the focus will be on ensuring any friction introduced by collapsing the two digital gardens into one will be minimal. So far this is mostly a question of disambiguation. On my public (only for me) wiki I had a &lt;code&gt;Personal Blog&lt;/code&gt; page which clearly meant &lt;a href=&quot;https://tjaddison.com/&quot;&gt;tjaddison.com&lt;/a&gt;. In the private (for our family) wiki that could mean any one of a number of personal blogs - thankfully renaming files automatically updates all references.&lt;/p&gt;
&lt;p&gt;Introducing Foam has also allowed us to revisit some of our tagging strategies - we used to use hyphens to create parent/child structures, but with Foam we get native support for parent/child tags.&lt;/p&gt;
&lt;p&gt;Once we&#39;ve worked through any teething issues I&#39;m very interested to explore what can be done with templates and frontmatter to create a much richer experience for some of the things we log in our digital garden (think books, restaurants, trips).&lt;/p&gt;
</description>
      <pubDate>Sat, 09 Jul 2022 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2022/07/migrating-my-digital-garden-from-wikilens-to-foam-and-taking-it-private/</guid>
    </item>
    <item>
      <title>What are the costs of an engineering career progression framework?</title>
      <link>https://tjaddison.com/blog/2022/04/what-are-the-costs-of-an-engineering-career-progression-framework/</link>
      <description>&lt;p&gt;I&#39;ve rarely seen the costs / return on investment (ROI) mentioned when discussing career progression frameworks. It turns out there are lots of interesting costs to be aware of, and some of them can be significant. If you&#39;re planning to roll out a framework, being aware of the costs as well as the benefits will increase the chance of your framework coming down on the right side of the ROI calculation.&lt;/p&gt;
&lt;p&gt;I was inspired to dig into this by Yvonne Lam&#39;s thread &lt;a href=&quot;https://twitter.com/yvonnezlam/status/1515425807438528514&quot;&gt;what needs do levels serve&lt;/a&gt;, which contains the insightful question &lt;em&gt;&amp;quot;what problems are orgs choosing to have when they introduce levels&amp;quot;&lt;/em&gt;. The related question of &lt;em&gt;&amp;quot;how else can could those needs be served&amp;quot;&lt;/em&gt; is also fascinating, but we&#39;ll stick to costs for now.&lt;/p&gt;
&lt;h2 id=&quot;visible-costs&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/04/what-are-the-costs-of-an-engineering-career-progression-framework/#visible-costs&quot;&gt;Visible Costs&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Starting with the costs that are easiest to see, these are costs that will be incurred and are directly linked to building and leveraging a framework.&lt;/p&gt;
&lt;p&gt;The first cost is &lt;strong&gt;building the framework&lt;/strong&gt;. Even with a wealth of &lt;a href=&quot;https://www.progression.fyi/&quot;&gt;inspiration to draw from&lt;/a&gt; the first draft of the framework will take several hours. Iterating towards a version you&#39;re ready to roll out will take dozens of hours from multiple people, with the cost increasing proportional to the ambition and complexity of your framework (how many roles? how many levels?).&lt;/p&gt;
&lt;p&gt;Just like any other product you build, the initial cost pales next to the cost to &lt;strong&gt;maintain the framework&lt;/strong&gt;. Technology definitely won&#39;t stand still, and hopefully neither will your company. There will be bugs and inconsistencies in the framework that will only be uncovered as you start to use it. A rule of thumb is probably whatever it took to build you&#39;ll be spending in annual maintenance costs if you want to keep it relevant.&lt;/p&gt;
&lt;p&gt;A well built and maintained framework will need to be used to actually deliver any value - which means you&#39;re going to have to spend time &lt;strong&gt;training people to use and apply the framework&lt;/strong&gt;. If you&#39;re going to use your framework for career growth as well as recruiting, this is potentially going to mean you&#39;ll also need to &lt;strong&gt;align your recruiting evaluation with your framework&lt;/strong&gt;. This is going to be both a large upfront investment as well as ongoing work for every employee and interview loop.&lt;/p&gt;
&lt;p&gt;I don&#39;t see any of these costs as avoidable if you want to build an effective framework. The only suggestion I&#39;d offer is ensure you focus on &#39;good enough&#39; at each step and assemble the smallest possible group of people to own the framework. The heuristic I&#39;ve most often used is if adding another person doesn&#39;t meaningfully increase the diversity of the group, don&#39;t make the group any larger.&lt;/p&gt;
&lt;p&gt;Now we&#39;re done with the visible and fairly innocuous costs, let&#39;s dig into the tricky ones.&lt;/p&gt;
&lt;h2 id=&quot;invisible-costs&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/04/what-are-the-costs-of-an-engineering-career-progression-framework/#invisible-costs&quot;&gt;Invisible Costs&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The largest cost of a framework is mostly invisible, and is related to incentives. If the framework does not perfectly align with delivering customer value, employees will be incentivised to make decisions based on &lt;strong&gt;personal progression over customer value&lt;/strong&gt;. This ranges from individuals making decisions about the work they&#39;ll pursue, to the managers who will need to make decisions about which projects to give to certain individuals &lt;em&gt;to help them secure a promotion&lt;/em&gt;. Incentives are &lt;a href=&quot;https://fs.blog/bias-incentives-reinforcement/&quot;&gt;tremendously powerful&lt;/a&gt;, and so this is a cost to ignore at your peril.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Pursuing promotion is a perfectly rational motivation. Attempts to paint it as an employee failing - when hundreds of thousands of dollars in a raise can be on the line - typically comes from someone in a position of overwhelming privilege.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Frameworks will also create many opportunities for people to be &lt;strong&gt;dissatisfied with their position in the framework&lt;/strong&gt;. The costs here can range from temporary demotivation, to the loss of an employee, to the inability to hire an employee whose only issue is a perceived (or real) mis-titling. In an extreme situation you may be faced with a candidate who would be a stellar performer in the role, but you cannot hire them as they don&#39;t fit into your framework.&lt;/p&gt;
&lt;p&gt;These two costs are significant enough that for me they eclipse the visible costs, and should be front and center when building and rolling out a progression framework.&lt;/p&gt;
&lt;p&gt;The final cost I&#39;ll cover is the &lt;strong&gt;illusion of objectivity&lt;/strong&gt;. Being more objective is a common reason cited to build a framework, but most (all?) frameworks have just moved the unconscious bias around. Almost every promotion will be tied to the opportunities afforded to a given individual, which will come back to bias and inclusion. Do you think a phenomenal framework would have changed Susan&#39;s &lt;a href=&quot;https://www.susanjfowler.com/blog/2017/2/19/reflecting-on-one-very-strange-year-at-uber&quot;&gt;very strange year at Uber&lt;/a&gt;? Anywhere you catch yourself discussing something that is &lt;em&gt;more meritocratic&lt;/em&gt; remember &lt;a href=&quot;https://gap.hks.harvard.edu/paradox-meritocracy-organizations&quot;&gt;the paradox of meritocracy&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;so-no-framework&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/04/what-are-the-costs-of-an-engineering-career-progression-framework/#so-no-framework&quot;&gt;So, no framework?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I don&#39;t see an alternative to not having a framework, though I&#39;d like to see us as an industry propose and test alternatives. Although &lt;a href=&quot;https://pattymccord.com/book/&quot;&gt;Powerful&lt;/a&gt; makes a very compelling case for no framework/levels, it glosses over the significant diversity &amp;amp; inclusion challenges a framework can assist with. That alone is reason enough for me to continue to advocate for a framework, even with the caveat around the illusion of objectivity.&lt;/p&gt;
</description>
      <pubDate>Sat, 30 Apr 2022 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2022/04/what-are-the-costs-of-an-engineering-career-progression-framework/</guid>
    </item>
    <item>
      <title>Writing a simple Pocket app in Node.js with no dependencies</title>
      <link>https://tjaddison.com/blog/2022/03/writing-a-simple-pocket-app-in-nodejs-with-no-dependencies/</link>
      <description>&lt;p&gt;As I&#39;ve started using &lt;a href=&quot;https://getpocket.com/&quot;&gt;Pocket&lt;/a&gt; to track articles I&#39;ve read/would like to read, I wanted to build a workflow that I could use when writing my &lt;a href=&quot;https://tjaddison.com/blog/tags/#Links&quot;&gt;Link Roundups&lt;/a&gt;. At the same time, I&#39;ve been wondering how productive you can be with no dependencies. A simple Pocket app to download my saved articles and write them to a file felt like a good test.&lt;/p&gt;
&lt;p&gt;The source code is available on &lt;a href=&quot;https://github.com/taddison/PocketDumper&quot;&gt;GitHub as PocketDumper&lt;/a&gt; - the rest of this article focuses on the thought process rather than how it works.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is my personal experience for a set of tools that has limited requirements. On larger projects, or in your tools - you may find the dependencies, frameworks, etc. to be invaluable. They have a place and the no dependencies approach is definitely not appropriate everywhere!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;why-no-dependencies&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/03/writing-a-simple-pocket-app-in-nodejs-with-no-dependencies/#why-no-dependencies&quot;&gt;Why no dependencies?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Building tools to make my life easier is something I&#39;ve been doing for a long time. As those tools have aged they occasionally require moderate to heroic efforts to get them working again.&lt;/p&gt;
&lt;p&gt;A few months ago I moved from Windows to macOS and I&#39;ve had a lot of tools that were built against platform-specific toolchains or dependencies suddenly demand a lot of effort to get them working. If switching OS is rare, I&#39;d rank encountering issue with dependency updates on almost anything built against a modern web toolchain/stack to be common. The blog post I wrote about &lt;a href=&quot;https://tjaddison.com/blog/2021/03/updating-babel-eslint-to-babeleslint-parser-for-react-apps/&quot;&gt;updating babel-eslint to @babel/eslint-parser&lt;/a&gt; remains the most popular post on my site by a large margin - I don&#39;t think I&#39;m alone in suffering from upgrade hell.&lt;/p&gt;
&lt;p&gt;The tools that have required the least TLC are shell scripts (luckily for me recent versions of &lt;a href=&quot;https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell&quot;&gt;PowerShell&lt;/a&gt; run cross-platform), though the developer experience is not ideal - especially when they start to grow into miniature applications rather than tools.&lt;/p&gt;
&lt;p&gt;Containers seem to be the logical answer here, but I&#39;ve experienced both compatibility issues with the move to Apple Silicon and the - in my mind - larger issue that the first-run time can be atrocious (time and energy spent to run what amounts to a trivial script).&lt;/p&gt;
&lt;p&gt;If I was a stronger C/C++ programmer I think I&#39;d be writing all of my tools in that language. Although I&#39;m not building my tools for the &lt;a href=&quot;https://longnow.org/&quot;&gt;long now&lt;/a&gt;, I&#39;d wager that if they were in C++ they&#39;d probably work just fine in a hundred years. Instead, I&#39;ve settled on JavaScript and &lt;a href=&quot;https://nodejs.org/&quot;&gt;Node.js&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;how-can-you-say-no-dependencies-and-then-pick-node-havent-you-heard-about-node-modules&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/03/writing-a-simple-pocket-app-in-nodejs-with-no-dependencies/#how-can-you-say-no-dependencies-and-then-pick-node-havent-you-heard-about-node-modules&quot;&gt;How can you say no dependencies and then pick Node? Haven&#39;t you heard about node_modules?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It has been a very long time since I&#39;ve started a node project and not &lt;em&gt;immediately&lt;/em&gt; installed multiple packages (or started from a template/framework such as &lt;a href=&quot;https://create-react-app.dev/&quot;&gt;create-react-app&lt;/a&gt; or &lt;a href=&quot;https://nextjs.org/&quot;&gt;Next.js&lt;/a&gt;). Even with Next&#39;s &lt;a href=&quot;https://nextjs.org/blog/next-10-1#improved-installation-time&quot;&gt;efforts to reduce dependencies&lt;/a&gt; there&#39;s still 150+ dependencies installed along for a brand new project. On top of that, I can&#39;t think of the last project I created where I didn&#39;t start off with one of either &lt;a href=&quot;https://www.typescriptlang.org/&quot;&gt;TypeScript&lt;/a&gt; or &lt;a href=&quot;https://babeljs.io/&quot;&gt;Babel&lt;/a&gt; plus a bundler.&lt;/p&gt;
&lt;p&gt;But what if we gave all that up, what are we left with?&lt;/p&gt;
&lt;p&gt;Well, as of Node 17.5+ - it turns out we can get quite a lot done.&lt;/p&gt;
&lt;h2 id=&quot;my-setup&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/03/writing-a-simple-pocket-app-in-nodejs-with-no-dependencies/#my-setup&quot;&gt;My Setup&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&#39;ve cheated a little on the no dependencies front by leveraging &lt;a href=&quot;https://volta.sh/&quot;&gt;Volta&lt;/a&gt; to provide me with access to &lt;a href=&quot;https://nodemon.io/&quot;&gt;nodemon&lt;/a&gt; in my toolchain. I&#39;m making the assumption that JavaScript and Node are going to be around for a long time, and so global tools like &lt;code&gt;nodemon&lt;/code&gt; will be accessible. Even if Volta disappears, I&#39;m confident &lt;code&gt;npm install -g&lt;/code&gt; will still be an option.&lt;/p&gt;
&lt;p&gt;To get type checking without TypeScript I&#39;m using &lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;VS Code&lt;/a&gt; with a &lt;a href=&quot;https://code.visualstudio.com/docs/languages/jsconfig&quot;&gt;jsconfig&lt;/a&gt; file. This doesn&#39;t give me anywhere near the same experience as I get with a full TypeScript project, but it definitely gives me enough to miss common errors - type annotations are still possible with JSDoc comments.&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;compilerOptions&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;target&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;es2021&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;module&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;es2022&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;checkJs&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&#39;m also taking advantage of the fact that &lt;code&gt;fetch&lt;/code&gt; is included with Node from 17.5+ (behind a flag) to remove the need for &lt;code&gt;node-fetch&lt;/code&gt; or similar packages. It&#39;s due to be included without the flag in Node 18.&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;node&lt;/span&gt; --experimental-fetch index.js&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// pocketApi.js&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;getRequestToken&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;consumerKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;endpoints&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;GetRequestToken&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;POST&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    headers&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;consumer_key&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; consumerKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      redirect_uri&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;/// elided...&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;My &lt;code&gt;index.js&lt;/code&gt; file also takes advantage of top-level await which has been available since Node 14.8 as long as the file or project is set to &lt;a href=&quot;https://nodejs.org/api/packages.html#determining-module-system&quot;&gt;ESM rather than CJS&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// index.js&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; getUserData &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./util.js&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; AccessToken&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ConsumerKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Since &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getUserData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Along with this there are now a lot of promise-based APIs available that mean callback&#39;s are no longer needed for things like &lt;a href=&quot;https://nodejs.org/api/readline.html&quot;&gt;readline&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; readline &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;node:readline/promises&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; stdin &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; input&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; stdout &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; output &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;node:process&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; rl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; readline&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createInterface&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; input&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; output &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; rl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;question&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Press enter once you have authorized the application&#92;r&#92;n&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are just a small set of the enhancements that have landed in Node (and the broader JavaScript ecosystem) in the last few years. It&#39;s a significant amount of effort to get up to speed but looking back at tools I&#39;ve authored 2, 3, or even 5 years ago - they could all be much smaller and take on less dependencies.&lt;/p&gt;
&lt;p&gt;I&#39;m not sure I&#39;d go back and rewrite them (excepting some dependency upgrade torture), but for future tools I&#39;ll certainly check what&#39;s available in native node/modern version of JavaScript, before reaching for a dependency.&lt;/p&gt;
&lt;h2 id=&quot;whats-next&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/03/writing-a-simple-pocket-app-in-nodejs-with-no-dependencies/#whats-next&quot;&gt;What&#39;s next?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The only thing I&#39;ve really felt that was missing was a good test runner, although I&#39;m fairly confident that for the kind of tools I&#39;m building I&#39;ll get pretty close with a little bit of &lt;a href=&quot;https://nodejs.org/api/assert.html&quot;&gt;assert&lt;/a&gt;. I&#39;ve also not had a look at the ecosystem to see if using a test runner via the Volta toolchain might also work for me.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Update: A little research shows that Node 18 is going to get a &lt;a href=&quot;https://github.com/nodejs/node/issues/40954&quot;&gt;built-in test-runner&lt;/a&gt;. What wonderful timing!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I was initially sceptical I&#39;d get anything useful done with no dependencies. After completing this exercise I reviewed the tools I&#39;ve found enduringly useful and they all involve a fairly small set of operations - filesystem interactions, http calls, and manipulation of in-memory data structures. With ES2022 and Node 18 (particularly if &lt;a href=&quot;https://github.com/tc39/proposal-temporal&quot;&gt;Temporal&lt;/a&gt; lands) the surface area of &#39;native Node&#39; has never looked so compelling.&lt;/p&gt;
</description>
      <pubDate>Thu, 31 Mar 2022 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2022/03/writing-a-simple-pocket-app-in-nodejs-with-no-dependencies/</guid>
    </item>
    <item>
      <title>Benchmarking .NET’s Dictionary.TryGetValue by key length and runtime</title>
      <link>https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/</link>
      <description>&lt;p&gt;How much faster is it to use a shorter key for a string-keyed dictionary? And how does that vary based on .NET 6.0 vs. .NET Framework 4.8? The &lt;a href=&quot;https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-6/&quot;&gt;avalanche of performance improvements in the runtime&lt;/a&gt; had me convinced the newer runtime would be faster, but by how much, and what order of magnitude was the impact of key length on performance (nano, micro, or milliseconds)?&lt;/p&gt;
&lt;p&gt;We&#39;ll start with the results:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/9I51taFvyh-295.avif 295w, https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/9I51taFvyh-590.avif 590w, https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/9I51taFvyh-885.avif 885w, https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/9I51taFvyh-1180.avif 1180w, https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/9I51taFvyh-1475.avif 1475w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/9I51taFvyh-295.webp 295w, https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/9I51taFvyh-590.webp 590w, https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/9I51taFvyh-885.webp 885w, https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/9I51taFvyh-1180.webp 1180w, https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/9I51taFvyh-1475.webp 1475w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/9I51taFvyh-295.jpeg 295w, https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/9I51taFvyh-590.jpeg 590w, https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/9I51taFvyh-885.jpeg 885w, https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/9I51taFvyh-1180.jpeg 1180w, https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/9I51taFvyh-1475.jpeg 1475w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Benchmark results&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/9I51taFvyh-295.jpeg&quot; width=&quot;1475&quot; height=&quot;941&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h2 id=&quot;benchmark-setup&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/#benchmark-setup&quot;&gt;Benchmark Setup&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The source code is &lt;a href=&quot;https://github.com/taddison/dotnet-benchmarks/tree/main/DictionaryKeyLookup&quot;&gt;on GitHub&lt;/a&gt;. The method I&#39;ve benchmarked is:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;TryGetValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; _dictionary&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TryGetValue&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;?&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The benchmark supports running that for keys of various length (I did 1 to 201 in increments of 25) over 5 runtimes, leveraging &lt;a href=&quot;https://benchmarkdotnet.org/articles/features/parameterization.html#sample-introargumentssource&quot;&gt;BenchmarkDotNet&#39;s arguments source&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;csproj&lt;/code&gt; file to support multiple runtimes looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Project&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;Sdk&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;Microsoft.NET.Sdk&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;PropertyGroup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;OutputType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Exe&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;OutputType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;TargetFrameworks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;net6.0;net5.0;netcoreapp3.1;net48;netcoreapp2.1&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;TargetFrameworks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;LangVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;7.3&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;LangVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;PropertyGroup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;ItemGroup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;PackageReference&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;Include&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;BenchmarkDotNet&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;Version&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;0.13.1&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;ItemGroup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;Project&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;benchmark-results-all-runtimes&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/#benchmark-results-all-runtimes&quot;&gt;Benchmark Results - All Runtimes&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/O3N3wCizaO-295.avif 295w, https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/O3N3wCizaO-590.avif 590w, https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/O3N3wCizaO-885.avif 885w, https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/O3N3wCizaO-1180.avif 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/O3N3wCizaO-295.webp 295w, https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/O3N3wCizaO-590.webp 590w, https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/O3N3wCizaO-885.webp 885w, https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/O3N3wCizaO-1180.webp 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/O3N3wCizaO-295.jpeg 295w, https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/O3N3wCizaO-590.jpeg 590w, https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/O3N3wCizaO-885.jpeg 885w, https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/O3N3wCizaO-1180.jpeg 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;All runtimes benchmark results&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/O3N3wCizaO-295.jpeg&quot; width=&quot;1180&quot; height=&quot;608&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;The runtimes cluster into a slow group (.NET Framework 4.8, .NET Core 2.1) and a fast group (.NET Core 3.1, .NET 5.0, .NET 6.0). On the machine I used (and given the benchmark run durations) I&#39;m attributing the deltas between the fast runtimes to noise, as repeated runs would sometimes change the ordering. This isn&#39;t surprising as we&#39;re talking about a handful of nanoseconds between them in many cases, which is a couple of processor cycles at best.&lt;/p&gt;
&lt;p&gt;On the machine I ran the benchmarks on the results show a cost of around 10-15ns at a key length of 1 character, increasing by 1ns/character for the slow group, and 0.5ns/char in the fast group. These are incredibly small numbers (2-4 processor cycles on the CPU I benchmarked on), and they&#39;re worth looking at alongside more common &lt;a href=&quot;https://colin-scott.github.io/personal_website/research/interactive_latency.html&quot;&gt;latency numbers&lt;/a&gt;. Using the .NET 6.0 runtime event at a key length of around 200 characters the cost of that lookup is about the same as accessing RAM.&lt;/p&gt;
&lt;h2 id=&quot;takeaways&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/#takeaways&quot;&gt;Takeaways&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Outside of code on a very hot path this probably doesn&#39;t matter (though if in doubt, benchmark and trace to be sure!). Being thoughtful with key length will improve performance (I&#39;ve seen examples where namespaces were used to prefix keys for uniqueness - and some namespaces can be &lt;em&gt;long&lt;/em&gt;). If getting every last drop of performance is essential, storing data in a field/property is going to be faster (a shared dictionary is often something that is convenient to use).&lt;/p&gt;
&lt;p&gt;When it comes to field vs. property there &lt;a href=&quot;https://till.red/b/1/&quot;&gt;really isn&#39;t any difference&lt;/a&gt; (I&#39;d be curious to see if anything differs in .NET 6, but not curious enough to run that benchmark).&lt;/p&gt;
&lt;p&gt;If you&#39;d like to read more about low-level details on memory performance (while we&#39;re in the realm of nanoseconds it&#39;s helpful to understand what various activities &#39;cost&#39;), &lt;a href=&quot;https://stackoverflow.com/questions/4087280/approximate-cost-to-access-various-caches-and-main-memory&quot;&gt;this Stack Overflow post&lt;/a&gt; has some great answers/comments/links.&lt;/p&gt;
</description>
      <pubDate>Mon, 28 Feb 2022 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2022/02/benchmarking-nets-dictionarytrygetvalue-by-key-length-and-runtime/</guid>
    </item>
    <item>
      <title>Early thoughts on switching to Apple silicon and macOS from Windows</title>
      <link>https://tjaddison.com/blog/2022/01/early-thoughts-on-switching-to-apple-silicon-and-macos-from-windows/</link>
      <description>&lt;p&gt;I&#39;ve been a Windows power user (personally and professionally) for over 20 years. In December 2020 I switched my primary machine to an M1 MacBook Pro and macOS Monterey, from a Surface Book 3 and Windows 10. After two months with the setup I&#39;m sharing my early thoughts on the transition.&lt;/p&gt;
&lt;p&gt;My workload consists primarily of using the Microsoft Office suite (someone once jokingly told me moving deeper into management meant switching an IDE out for Word/Outlook - it wasn&#39;t a joke, and they forgot to mention Excel!), with some recreational coding (Node/React) as well as professional SQL Server development. I spend a lot of time consuming written content on the web, and even more time in video calls (using Teams) on workdays.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The specific devices I&#39;m comparing are a 13.5&amp;quot; Surface Book 3 i7/32GB/512GB SSD and a 14&amp;quot; M1 MacBook Pro 10-core/16GB/512GB SSD. At the time of writing the Mac is about $400 cheaper than the Surface Book.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;os&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/01/early-thoughts-on-switching-to-apple-silicon-and-macos-from-windows/#os&quot;&gt;OS&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After a relatively painful (yet short) learning curve on shortcuts, I&#39;m mostly past the &#39;who moved the cheese&#39; phase of learning the OS. I didn&#39;t find any particularly useful cheat-sheets or articles, mostly reading documentation (checking carefully for the version of the OS it applies to) or looking up shortcuts in the menu. Command+Shift+? is extremely useful for this (search within menus).&lt;/p&gt;
&lt;p&gt;One item I&#39;ve found myself missing is accessing menu items via the alt key - the solution to which appears to be either learn the shortcut (if the menu item has one), or use the menu search. Browsing the menu with the keyboard feels clunky and I find myself reaching for the trackpad a lot.&lt;/p&gt;
&lt;p&gt;Native window management (especially with multiple monitors) is poor, and after trying to live with the native options (which ended up with a lot of trackpad usage) I installed &lt;a href=&quot;https://rectangleapp.com/&quot;&gt;Rectangle&lt;/a&gt; (&lt;code&gt;brew install --cask rectangle&lt;/code&gt;) and have never looked back.&lt;/p&gt;
&lt;p&gt;Speaking of &lt;a href=&quot;https://brew.sh/&quot;&gt;Homebrew&lt;/a&gt; - it&#39;s how I wish I&#39;d installed everything. I used &lt;a href=&quot;https://chocolatey.org/&quot;&gt;Chocolatey&lt;/a&gt; off and on for Windows (and think &lt;a href=&quot;https://docs.microsoft.com/en-us/windows/package-manager/winget/&quot;&gt;winget&lt;/a&gt; may have a bright future), but the breadth of packages and support for homebrew blows them away.&lt;/p&gt;
&lt;p&gt;Overall I&#39;m still far more productive at OS tasks in Windows.&lt;/p&gt;
&lt;h2 id=&quot;terminal-and-shell&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/01/early-thoughts-on-switching-to-apple-silicon-and-macos-from-windows/#terminal-and-shell&quot;&gt;Terminal and Shell&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My setup was [Windows Terminal] and [PowerShell 7] (aka &lt;code&gt;pwsh&lt;/code&gt; or PowerShell Core). &lt;a href=&quot;https://iterm2.com/&quot;&gt;iTerm2&lt;/a&gt; (&lt;code&gt;brew install --cask iterm2&lt;/code&gt;) is a fantastic terminal, and the installation instructions for &lt;a href=&quot;https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-macos&quot;&gt;PowerShell 7 on macOS&lt;/a&gt; were easy to follow. Once installed, you need to set &lt;code&gt;pwsh&lt;/code&gt; as the default shell in iTerm2 (&lt;code&gt;which pwsh&lt;/code&gt; will tell you the path to the binary, and in iTerm preferences you want Profiles &amp;gt; Default &amp;gt; Command &amp;gt; Custom Shell).&lt;/p&gt;
&lt;p&gt;I had some issues getting homebrew working in &lt;code&gt;pwsh&lt;/code&gt;, which I fixed by adding the following line to my PowerShell profile (edit via &lt;code&gt;code $PROFILE&lt;/code&gt;):&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$Env&lt;/span&gt;:PATH &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;:/opt/homebrew/bin&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The documentation in general for Apple Silicon is harder to find, and PowerShell on Apple Silicon even more so.&lt;/p&gt;
&lt;p&gt;With homebrew working in PowerShell I got &lt;a href=&quot;https://ohmyposh.dev/&quot;&gt;oh-my-posh&lt;/a&gt; installed easily, on top of which I added &lt;a href=&quot;https://github.com/badmotorfinger/z&quot;&gt;Z&lt;/a&gt;. I&#39;ve not yet really finished my exploration of &lt;a href=&quot;https://www.chezmoi.io/&quot;&gt;chezmoi&lt;/a&gt;, and I&#39;d like to see what a repeat experience looks like with that (or even sharing configuration between Windows and macOS).&lt;/p&gt;
&lt;h2 id=&quot;office-suite&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/01/early-thoughts-on-switching-to-apple-silicon-and-macos-from-windows/#office-suite&quot;&gt;Office Suite&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My prior exposure to Excel on a Mac was a miserable one a few years ago, assisting a friend with what &lt;em&gt;should&lt;/em&gt; have been a quick pivot and some formulae and it turned out...there were a lot of missing features. Fast forward to today and I can only count one missing feature that I wanted to use (&lt;a href=&quot;https://support.microsoft.com/en-us/office/lambda-function-bd212d27-1cd1-4321-a34a-ccbf254b8b67&quot;&gt;The Lambda function&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The other tools (Word, Outlook) I can&#39;t say I&#39;ve noticed any feature gaps.&lt;/p&gt;
&lt;p&gt;Re-learning keyboard shortcuts has been the only pain-point, though not enough to have a serious impact on my productivity. My Excel usage is nowhere near that of someone who participates in the &lt;a href=&quot;https://www.fmworldcup.com/&quot;&gt;Financial Modelling World Cup&lt;/a&gt; - I only know enough to be mildly dangerous.&lt;/p&gt;
&lt;p&gt;I&#39;m about as productive on the Mac as I was on Windows.&lt;/p&gt;
&lt;h2 id=&quot;development&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/01/early-thoughts-on-switching-to-apple-silicon-and-macos-from-windows/#development&quot;&gt;Development&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For the bulk of my development work (which is mostly recreational - Node/React), this has been nothing but an improvement. Both in perceived and measured performance (I ran a lot of &lt;code&gt;Measure-Command&lt;/code&gt; to check install/build times) everything is snappier. Despite the Mac being cheaper than the Surface Book, for almost everything I could measure the Mac was anywhere from 2x to 10x faster.&lt;/p&gt;
&lt;p&gt;As most of my day is spent in some way interacting with JavaScript (VS Code, reading anything on the web) the performance difference is palpable.&lt;/p&gt;
&lt;p&gt;The Mac is hands-down the better experience, completing similar activities on my Windows machine feels sluggish.&lt;/p&gt;
&lt;h2 id=&quot;sql-server-development&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/01/early-thoughts-on-switching-to-apple-silicon-and-macos-from-windows/#sql-server-development&quot;&gt;SQL Server Development&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;No good news here. If you want to do any meaningful SQL Server development you&#39;re probably going to need a Windows device. While you can install &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-sql-edge/&quot;&gt;SQL Edge&lt;/a&gt; on the Mac for trivial development, that doesn&#39;t have much overlap with the kind of work I do (the list of &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-sql-edge/features#unsupported-features&quot;&gt;unsupported features&lt;/a&gt; is long).&lt;/p&gt;
&lt;p&gt;Even &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/azure-data-studio&quot;&gt;Azure Data Studio&lt;/a&gt; (&lt;code&gt;brew install --cask azure-data-studio&lt;/code&gt;) is a poor replacement for SQL Server Management Studio (SSMS).&lt;/p&gt;
&lt;p&gt;I&#39;m lucky that we leverage &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/virtual-desktop/overview&quot;&gt;Azure Virtual Desktop&lt;/a&gt; (AVD) at work which allows me to run an instance of SSMS on my Mac via application virtualization. This does require a lot of infrastructure to support, but is almost indistinguishable from running SSMS on a Windows machine.&lt;/p&gt;
&lt;p&gt;If I didn&#39;t have access to AVD I&#39;m not sure what I&#39;d do here - &lt;em&gt;probably&lt;/em&gt; develop against an &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-sql/database/sql-database-paas-overview&quot;&gt;Azure SQL Database&lt;/a&gt; with Azure Data Studio, and feel bad about it.&lt;/p&gt;
&lt;p&gt;The Surface Book is far superior here.&lt;/p&gt;
&lt;h2 id=&quot;overall-impressions&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2022/01/early-thoughts-on-switching-to-apple-silicon-and-macos-from-windows/#overall-impressions&quot;&gt;Overall Impressions&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I can&#39;t imagine switching back. Although the build quality of the Surface Book was good, the Mac is excellent. Any concerns I had about the keyboard and typing speed were quickly dispelled (I &lt;a href=&quot;https://flatgithub.com/taddison/my-data/blob/main/typing/results.csv?filename=typing%2Fresults.csv&amp;amp;sha=7ca028570c3bc25c8a51fc0120c88456048c29b2&quot;&gt;tested myself&lt;/a&gt; about the same on both devices).&lt;/p&gt;
&lt;p&gt;Battery life on the Mac is exceptional - I can get through a full working day of 8-10 hours use, whereas I&#39;d need the power adapter on the Surface Book. I&#39;ve never heard the fans on my Mac, unlike the Surface Book where even video calls would cause them to spin up.&lt;/p&gt;
&lt;p&gt;Even though I can&#39;t imagine switching back, if Windows performance could approach M1 performance - I&#39;d give that a try.&lt;/p&gt;
</description>
      <pubDate>Mon, 31 Jan 2022 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2022/01/early-thoughts-on-switching-to-apple-silicon-and-macos-from-windows/</guid>
    </item>
    <item>
      <title>Don&#39;t mistake their kindness for empathy</title>
      <link>https://tjaddison.com/blog/2021/12/dont-mistake-their-kindness-for-empathy/</link>
      <description>&lt;p&gt;When considering someone for a management role I&#39;ve seen people mistake kindness for empathy. Having a kind manager is definitely better than having an unkind manager, but to effectively manage people you should be looking for an empathetic manager.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I&#39;ve seen this happen on multiple occasions - in some cases an individual confuses their own kind behaviour for empathy! The rather low baseline for engineering managers probably plays a part here, where a table-stakes trait like kindness sets you apart as a potential manager. In addition to that, we continue to put people into management positions who are incapable and/or unqualified for the role because we have &lt;a href=&quot;https://lethain.com/getting-to-yes/&quot;&gt;no idea what to look for&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This doesn&#39;t mean that kindness isn&#39;t something I look for - it&#39;s actually one of the first things I want to see when evaluating people management capabilities:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Are they kind?&lt;/li&gt;
&lt;li&gt;Are they empathetic?&lt;/li&gt;
&lt;li&gt;Are they curious about their team?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;But before we get too far, we need to look a little closer at that &#39;kind&#39; word.&lt;/p&gt;
&lt;h2 id=&quot;you-cant-always-be-kind&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/12/dont-mistake-their-kindness-for-empathy/#you-cant-always-be-kind&quot;&gt;You can&#39;t always be kind&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Although advice like &lt;em&gt;&amp;quot;be unfailingly kind&amp;quot;&lt;/em&gt; and &lt;em&gt;&amp;quot;keep your feedback actionable, specific, and kind&amp;quot;&lt;/em&gt; sounds great, &lt;a href=&quot;https://chelseatroy.com/&quot;&gt;Chelsea Troy&lt;/a&gt; points out the problem with that statement, namely:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;...it assumes that the feedback giver has control over whether their feedback is perceived as kind. And they don’t.
&lt;em&gt;from &lt;a href=&quot;https://chelseatroy.com/2019/05/15/giving-and-receiving-feedback/&quot;&gt;Transcript from lecture: Giving and receiving feedback&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This goes beyond feedback, and extends to all interactions with multiple parties - kindness is really in the eye of the beholder, and so the best you can do is &lt;em&gt;act with kind intent&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Anyone who can share examples of where they&#39;ve experienced a gap between their intent (be kind) and the impact (not perceived as kind) does well here. The best candidates will be able to share examples of where they&#39;ve changed the way they interact to minimise that gap (a great example Chelsea shares is: not giving someone feedback if they&#39;re actually looking for validation).&lt;/p&gt;
&lt;p&gt;Being able to act with kind intent requires empathy, which we&#39;ll come onto next.&lt;/p&gt;
&lt;h2 id=&quot;empathy-isnt-only-for-kind-people&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/12/dont-mistake-their-kindness-for-empathy/#empathy-isnt-only-for-kind-people&quot;&gt;Empathy isn&#39;t only for kind people&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The reason I&#39;m looking for kindness before empathy is because being empathetic isn&#39;t locked away down the nice-person skill tree. I&#39;d argue that some of the most hurtful people are incredibly empathetic and leverage that to cause significant harm. Testing for empathy without controlling for kindness can let brilliant jerks through, so I&#39;d never start with empathy.&lt;/p&gt;
&lt;p&gt;Empathy is often presented alongside sympathy and compassion:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sympathy&lt;/strong&gt; - I am sorry you&#39;re going through that&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Empathy&lt;/strong&gt; - I can imagine what it must feel like to be going through that&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compassion&lt;/strong&gt; - What can I do to help?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This framing sticks with people, such that being empathetic benefits from the positive associations of sympathy and compassion, despite empathy being somewhat orthogonal to those traits.&lt;/p&gt;
&lt;p&gt;Assuming you&#39;ve verified kind intent, checking for empathy typically starts with the question &lt;em&gt;&amp;quot;what were they thinking?&amp;quot;&lt;/em&gt;. This can either be used in made-up scenarios (you request a meeting to discuss progress on a project which isn&#39;t going well), but are best when you&#39;re discussing real scenarios (delivering a performance review, promotion, termination, mediating a heated debate).&lt;/p&gt;
&lt;p&gt;The best candidates will be able to answer a wide variety of follow ups:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What were they trying to achieve?&lt;/li&gt;
&lt;li&gt;What were they thinking but didn&#39;t feel they could say, and why?&lt;/li&gt;
&lt;li&gt;What was their biggest concern?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But empathy alone isn&#39;t why they&#39;ll excel here, because empathy works best when paired with a deep knowledge of the individual.&lt;/p&gt;
&lt;h2 id=&quot;curious-about-their-team&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/12/dont-mistake-their-kindness-for-empathy/#curious-about-their-team&quot;&gt;Curious about their team&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Every new manager starts out with almost zero knowledge of their new team. No amount of empathy or kind intent can make up for this, and so curiosity and a commitment to learn more about their team is crucial.&lt;/p&gt;
&lt;p&gt;I&#39;ve found this is best evaluated alongside empathy with the follow-up question &lt;em&gt;&amp;quot;and why did you think that?&amp;quot;&lt;/em&gt;. I&#39;m not sure if there is a clear distinction on good vs. great as having &lt;em&gt;any&lt;/em&gt; answer is a good sign (most candidates stumble at empathy).&lt;/p&gt;
&lt;p&gt;Interestingly, I&#39;ve seen a mix of both talent and skill at work here. Some people are more naturally talented (curious) and will always look to learn more. Other people aren&#39;t, but are able to make up for that deficit with a system/approach they apply. In either case, one common factor is consistency. If you don&#39;t show up and spend time with your team every week, it doesn&#39;t matter how good your system is/how talented you are - you&#39;ll never learn anything.&lt;/p&gt;
&lt;h3 id=&quot;aside-projection-and-bias&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/12/dont-mistake-their-kindness-for-empathy/#aside-projection-and-bias&quot;&gt;Aside: Projection and Bias&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Be prepared for this line of questioning to be the first time someone has really introspected about why they thought something - I&#39;d consider this a fairly advanced skill. As a consequence, you may end up in a coaching session on projection and/or bias.&lt;/p&gt;
&lt;p&gt;As everyone is unique we&#39;re bound to attempt to pattern match behaviours - and the set of behaviours we&#39;re most familiar with is our own. When you ask someone why an engineer really wanted to learn a new technology, a common answer (when you really dig) is &lt;em&gt;&amp;quot;because that&#39;s what I was interested in doing at that stage in my career&amp;quot;&lt;/em&gt;. Projecting your own motivations onto someone else isn&#39;t necessarily wrong (and with experience you gain a larger catalogue of patterns to match against), but awareness you&#39;re doing it is important.&lt;/p&gt;
&lt;p&gt;Projection is less likely when you&#39;re dealing with someone who is different, and so here is where you need to watch out for bias. As an example, consider a discussion about an engineer who always volunteers to take minutes, and when you ask &lt;em&gt;&amp;quot;why do you think she does that?&amp;quot;&lt;/em&gt; the candidate replies &lt;em&gt;&amp;quot;because she is naturally talented at organizing things&amp;quot;&lt;/em&gt;. Would your answer have changed if the engineer was male? Did you ever probe to validate your assumption? Could this have been a case of &lt;a href=&quot;https://noidea.dog/glue&quot;&gt;glue work&lt;/a&gt; automatically getting assigned to the only female engineer on the team?&lt;/p&gt;
&lt;p&gt;These kind of conversations (which are really great coaching opportunities) happen more often in regular 1:1s, but I have ended up here in a few interviews. The first interview where the conversation turned into a discussion on unconscious bias I wasn&#39;t particularly ready for (and neither me or the candidate enjoyed that segment). If you&#39;re going to pursue this line of questioning, be ready to have the full conversation.&lt;/p&gt;
&lt;p&gt;If you&#39;d like to read more on bias I&#39;d strongly encourage you to work through all the links at the start of &lt;a href=&quot;https://chelseatroy.com/2018/05/24/why-your-efforts-to-make-your-company-inclusive-arent-working/&quot;&gt;Why your efforts to make your company inclusive aren&#39;t working&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;in-summary&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/12/dont-mistake-their-kindness-for-empathy/#in-summary&quot;&gt;In summary&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To be a great people manager you want to look for someone who:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Acts with kind intent&lt;/li&gt;
&lt;li&gt;Is empathetic&lt;/li&gt;
&lt;li&gt;Is curious about their team/colleagues&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;More generally, these are useful skills to work on in whatever role you have, irrespective of whether you are or ever want to be a manager. They&#39;re also skills that can be improved with practice - so even if you don&#39;t consider yourself empathetic, try asking yourself &lt;em&gt;&amp;quot;what were they thinking&amp;quot;&lt;/em&gt; after the next interesting interaction you have.&lt;/p&gt;
</description>
      <pubDate>Fri, 31 Dec 2021 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2021/12/dont-mistake-their-kindness-for-empathy/</guid>
    </item>
    <item>
      <title>Interesting Links - November 2021</title>
      <link>https://tjaddison.com/blog/2021/11/interesting-links---november-2021/</link>
      <description>&lt;p&gt;Links are broadly categorised into &lt;a href=&quot;https://tjaddison.com/blog/2021/11/interesting-links---november-2021/#development&quot;&gt;Development&lt;/a&gt;, &lt;a href=&quot;https://tjaddison.com/blog/2021/11/interesting-links---november-2021/#management-and-leadership&quot;&gt;Management and leadership&lt;/a&gt;, and the catch-all &lt;a href=&quot;https://tjaddison.com/blog/2021/11/interesting-links---november-2021/#miscellaneous&quot;&gt;Miscellaneous&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;development&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/11/interesting-links---november-2021/#development&quot;&gt;Development&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Capturing my frustrations on what an &lt;code&gt;npm-outdated&lt;/code&gt; looks like for my Gatsby-powered blog, Ru Singh&#39;s &lt;a href=&quot;https://rusingh.com/waving-thankful-goodbye-to-static-websites-and-more/&quot;&gt;waving goodbye to static websites and more&lt;/a&gt; does make the &#39;DIY&#39; option look like a poor choice. Every time I think about switching to Wordpress (or something else) I run through the framework I used last time I switched my blog (&lt;a href=&quot;https://tjaddison.com/blog/2019/09/migrating-from-jekyll-to-gatsby/&quot;&gt;back in 2019&lt;/a&gt;) and always land on static sites. For now.&lt;/p&gt;
&lt;p&gt;One important feature for me is cost - ideally free, and &lt;a href=&quot;https://github.com/255kb/stack-on-a-budget&quot;&gt;stack on a budget&lt;/a&gt; is a good place to look for inspiration. Another feature is performance, and &lt;a href=&quot;https://www.zachleat.com/web/speedlify/&quot;&gt;Speedlify&lt;/a&gt; can let you track performance over time (though Lighthouse scores are not &lt;a href=&quot;https://www.zachleat.com/web/lighthouse-deception/&quot;&gt;without their challenges&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Another month goes by and the GitHub issues for &lt;a href=&quot;https://github.com/vercel/next.js/discussions/19065&quot;&gt;optimize images during next build&lt;/a&gt;...maybe inches towards being implemented? I&#39;ll probably have to ignore this and rely on the default &lt;code&gt;next/image&lt;/code&gt; component, though I&#39;m not a fan of being locked into Vercel (as a free host). When the time comes to migrate, the &lt;a href=&quot;https://github.com/hashicorp/next-mdx-remote&quot;&gt;next-mdx-remote&lt;/a&gt; library is probably how I&#39;ll get the MD/MDX used (there&#39;s even a &lt;a href=&quot;https://github.com/vercel/next.js/tree/canary/examples/with-mdx-remote&quot;&gt;starter template&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Finally, if you find yourself wanting a reminder of what the relentless pursuit of pragmatism looks like, these &lt;a href=&quot;https://en.m.wikiquote.org/wiki/Rasmus_Lerdorf&quot;&gt;quotes from Rasmus Lerdorf&lt;/a&gt; (creator of PHP) are worth reviewing. As a taster - &lt;em&gt;&amp;quot;I actually hate programming, but I love solving problems.&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;management-and-leadership&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/11/interesting-links---november-2021/#management-and-leadership&quot;&gt;Management and Leadership&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Scaling security across an organization is &lt;em&gt;hard&lt;/em&gt; - Clint Gibler&#39;s presentation &lt;a href=&quot;https://docs.google.com/presentation/d/1lfEvXtw5RTj3JmXwSQDXy8or87_BHrFbo1ZtQQlHbq0/view#slide=id.g6555b225cd_0_1069&quot;&gt;How to 10X your security&lt;/a&gt; is an excellent starting point (and probably covers stuff that most orgs won&#39;t even get to in their end-state). Clint also maintains the excellent infosec newsletter &lt;a href=&quot;https://tldrsec.com/&quot;&gt;tldrsec.com&lt;/a&gt;, which reduces the firehose of infosec news to a ...slightly smaller firehose?&lt;/p&gt;
&lt;p&gt;A common criticism of most business books I read is &lt;em&gt;&#39;that could have been a long-form article&#39;&lt;/em&gt;. Imagine my delight when I discovered &lt;a href=&quot;https://koolaidfactory.com/&quot;&gt;The Kool Aid Factory&lt;/a&gt;, a site full of zines (in this instance: long-form articles) that cover various aspects of organizational design. In addition to having a tonne of content that will get you thinking, the site&#39;s filters allow you to focus on what matters to you (though I ended up devouring everything - no filters for me).&lt;/p&gt;
&lt;p&gt;From articles that resisted becoming books, to a presentation that resisted becoming a book - &lt;a href=&quot;https://komoroske.com/slime-mold/&quot;&gt;Coordination Headwind - How organizations are like slime molds&lt;/a&gt; covers the challenge of keeping an organization nimble as it grows.&lt;/p&gt;
&lt;p&gt;I&#39;ve always felt a little uneasy with the advice that states you should &lt;em&gt;&#39;never be the smartest person in the room&#39;&lt;/em&gt;, as it always felt like a crutch to lean on when you don&#39;t want to have a conversation about the values of a diverse team (smart sometimes being a codeword for cishet-white-male). A much better rebuttal can be found in &lt;a href=&quot;https://www.ribbonfarm.com/2014/11/05/dont-surround-yourself-with-smarter-people/&quot;&gt;don&#39;t surround yourself with smarter people&lt;/a&gt;, which is a long but rewarding read that switches out &lt;em&gt;smart&lt;/em&gt; for &lt;em&gt;differently free&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I don&#39;t think the dust has settled on the future of work (at least I &lt;em&gt;hope&lt;/em&gt; it hasn&#39;t, and that we&#39;re still in a phase of the pandemic that doesn&#39;t fully represent the new normal), but I do think there is a lot we can learn about the good &amp;amp; bad of remote from the natural experiment COVID-19 provided. The negative impact on bridging/weak/cross-team ties is what I was most interested in exploring in &lt;a href=&quot;https://www.nature.com/articles/s41562-021-01196-4.pdf&quot;&gt;The effects of remote work on collaboration among information workers&lt;/a&gt;. I definitely felt that being fully remote &#39;impacted serendipity&#39;, but that is much more rigorously formulated and explored in the paper. No solutions on offer here though - I wonder if more radical organizational changes are needed to succeed with a remote org (rather than taking the existing organizational structure and simply removing everyone from the office)?&lt;/p&gt;
&lt;p&gt;Regardless of remote or in-person, articles on &lt;a href=&quot;https://knowyourteam.com/blog/2017/12/01/how-to-have-an-honest-one-on-one-meeting-with-an-employee/&quot;&gt;how to have an honest one on one&lt;/a&gt;, fantastic &lt;a href=&quot;https://twitter.com/kaydacode/status/1458084282530992140&quot;&gt;threads on soliciting feedback as a manager&lt;/a&gt; or the kitchen-sink of resources (&lt;a href=&quot;https://docs.google.com/document/d/1R1O0OEsQpZcBcLheRlomDrmR2tyEpdRNFnjbLALmbH4/view&quot;&gt;How to Lead People and Be a Manager&lt;/a&gt;) are well worth revisiting in what is turning out to be a very challenging few months/years/new normal.&lt;/p&gt;
&lt;p&gt;I&#39;ve also ordered the book &lt;a href=&quot;https://pragprog.com/titles/jsengman/become-an-effective-software-engineering-manager/&quot;&gt;Become an effective software engineering manager&lt;/a&gt;, though I do wonder when I&#39;ll get round to reading it. Meanwhile, James has released a new book on effective remote work, and I&#39;ve still not finished going through his back-catalogue of his &lt;a href=&quot;https://www.theengineeringmanager.com/&quot;&gt;excellent posts on engineering management&lt;/a&gt;. An ever-expanding &lt;a href=&quot;https://fs.blog/the-antilibrary/&quot;&gt;anti library&lt;/a&gt; is a great problem to have.&lt;/p&gt;
&lt;h2 id=&quot;miscellaneous&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/11/interesting-links---november-2021/#miscellaneous&quot;&gt;Miscellaneous&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;people&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/11/interesting-links---november-2021/#people&quot;&gt;People&lt;/a&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;In addition to recently following the below, I also highly recommend their archives.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I first encountered &lt;a href=&quot;https://sive.rs/&quot;&gt;Derek Sivers&lt;/a&gt; thanks to his &lt;a href=&quot;https://sive.rs/now&quot;&gt;Now page&lt;/a&gt; (mine is still a TODO), but I stuck around for the thoughtful and clear writing on a wide range of topics.&lt;/p&gt;
&lt;p&gt;I can&#39;t remember where I first encountered &lt;a href=&quot;https://rachelbythebay.com/w/&quot;&gt;Rachel&#39;s writing&lt;/a&gt;, but I suspect it was a link to one of the (many, excellent) war stories she has shared. I found myself nodding along an awful lot, and appreciate someone sharing the unvarnished truth on how dysfunctional work can be.&lt;/p&gt;
&lt;p&gt;For longer, thought-provoking reads (also on a wide range of topics), the &lt;a href=&quot;https://astralcodexten.substack.com/&quot;&gt;writing of Scott Alexander&lt;/a&gt; never disappoints. Seeing Scott highlight and respond to some of the better comments in follow-up posts is a unique touch, and it helps to see the material discussed from different viewpoints.&lt;/p&gt;
&lt;p&gt;Finally, something that has so far defied my attempts to track via Feedly - &lt;a href=&quot;https://www.gwern.net/&quot;&gt;Gwern Branwen&#39;s website&lt;/a&gt;. I&#39;m not sure what to call it - digital garden, long content, evergreen blog - whatever it is, the thought process behind the site really resonated with me. In addition to deep and engaging material, the UX of the site is truly excellent (and quite necessary given all the fascinating links and references).&lt;/p&gt;
&lt;h3 id=&quot;tools&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/11/interesting-links---november-2021/#tools&quot;&gt;Tools&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For editing tables in markdown (or CSV for that matter), the &lt;a href=&quot;https://eviltester.github.io/grid-table-editor/&quot;&gt;grid table editor&lt;/a&gt; supports operations like adding/removing/moving columns, which is not much fun to do manually.&lt;/p&gt;
&lt;p&gt;Another great tool I&#39;ve started using recently to enhance a text (e.g. markdown) documentation is &lt;a href=&quot;https://monodraw.helftone.com/&quot;&gt;monodraw&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Only after hacking together a &lt;a href=&quot;https://github.com/taddison/personal-site/blob/main/scripts/newpost.mjs&quot;&gt;script&lt;/a&gt; to scaffold new blog posts I discovered &lt;a href=&quot;https://plopjs.com/&quot;&gt;plop&lt;/a&gt;, an incredibly versatile &#39;micro-generator framework&#39; for node - the combination of inquirer and handlebars is pretty versatile.&lt;/p&gt;
&lt;p&gt;I&#39;ve recently started using a Mac in addition to my Windows machine, and one of the first things I installed was PowerShell core. While tweaking my shell (starting with &lt;a href=&quot;https://www.ohmyposh.dev/docs/&quot;&gt;Oh My Posh&lt;/a&gt;) I gave the &lt;a href=&quot;https://github.com/badmotorfinger/z&quot;&gt;z PowerShell command&lt;/a&gt; a try, and I can&#39;t believe I didn&#39;t try it sooner, the amount of time I spend navigating directories has dropped dramatically.&lt;/p&gt;
&lt;h3 id=&quot;well-being&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/11/interesting-links---november-2021/#well-being&quot;&gt;Well-being&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A notable gap in the data I collect (see &lt;a href=&quot;https://quantifiedself.com/&quot;&gt;quantified self&lt;/a&gt;) is mental health - will I remain &#39;young minded&#39; as I age, and will I be free from any mental illness? For the former question I&#39;ve looked at &lt;a href=&quot;https://centerofinquiry.org/wp-content/uploads/2018/04/Ryff_Scales.pdf&quot;&gt;Ryff Scales&lt;/a&gt; and some tests on &lt;a href=&quot;https://yourmorals.org/&quot;&gt;Your Morals&lt;/a&gt;, and for the latter it seems positive mental health correlates with a decline in the incidence of mental illnesses (see the paper &lt;a href=&quot;https://ajph.aphapublications.org/doi/full/10.2105/AJPH.2010.192245&quot;&gt;Change in Level of Positive Mental Health as a Predictor of Future Risk of Mental Illness&lt;/a&gt;). A few open questions I have are what can be done to improve mental health, and how reliable is self-evaluations.&lt;/p&gt;
&lt;h3 id=&quot;typing&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/11/interesting-links---november-2021/#typing&quot;&gt;Typing&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Right now I&#39;m only considering it, but moving to a &lt;a href=&quot;https://jhelvy.shinyapps.io/splitkbcompare/&quot;&gt;split keyboard&lt;/a&gt; and learning a &lt;a href=&quot;https://dreymar.colemak.org/&quot;&gt;new keyboard layout&lt;/a&gt; might help maximise my ability to type effectively over time (and maybe boost my &lt;a href=&quot;https://www.keysleft.com/&quot;&gt;keys left&lt;/a&gt;). In anticipation of switching, I&#39;ve started to capture some basic &lt;a href=&quot;https://flatgithub.com/taddison/my-data?filename=typing%2Fresults.csv&quot;&gt;typing speed results&lt;/a&gt; with &lt;a href=&quot;https://monkeytype.com/&quot;&gt;monkeytype&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&#39;d like to consume more research on the topic, so far the only paper I&#39;ve completed was to confirm something I was already fairly certain of - tactile feedback matters for typing speed and accuracy (&lt;a href=&quot;https://engineering.purdue.edu/~hongtan/pubs/PDFfiles/C63_JRKimTan_HS2014.pdf&quot;&gt;A Study of Touch Typing Performance with Keyclick Feedback&lt;/a&gt;).&lt;/p&gt;
</description>
      <pubDate>Tue, 30 Nov 2021 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2021/11/interesting-links---november-2021/</guid>
    </item>
    <item>
      <title>Deploy an Azure AD protected App Service Website with Pulumi</title>
      <link>https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/</link>
      <description>&lt;p&gt;This post will walk through how to use &lt;a href=&quot;https://www.pulumi.com/&quot;&gt;Pulumi&lt;/a&gt; to deploy an &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/app-service/overview&quot;&gt;Azure App Service&lt;/a&gt; application secured with &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/app-service/overview-authentication-authorization&quot;&gt;Easy Auth&lt;/a&gt;. Under the default configuration only authenticated users will be able to access the application, without any custom code (easy auth places an authentication/authorization middleware in front of your app). If you&#39;d like to jump straight to the code you can see a &lt;a href=&quot;https://github.com/taddison/pulumi-csharp-azure-examples/tree/main/easyauth-webapp&quot;&gt;full example project on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I&#39;ll be using Azure Active Directory in this example, though easy auth also supports Microsoft (personal account), Google, Facebook, Twitter, and OpenID Connect.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&#39;m assuming you&#39;ve already completed the &lt;a href=&quot;https://www.pulumi.com/docs/get-started/azure/begin/&quot;&gt;Pulumi Azure pre-requisites&lt;/a&gt; (or similar), and have the appropriate permissions in your tenant and Azure subscription. When we&#39;re done we&#39;ll have deployed a Pulumi stack with the following resources:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/AAT-n50vte-295.avif 295w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/AAT-n50vte-590.avif 590w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/AAT-n50vte-885.avif 885w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/AAT-n50vte-1180.avif 1180w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/AAT-n50vte-1475.avif 1475w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/AAT-n50vte-295.webp 295w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/AAT-n50vte-590.webp 590w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/AAT-n50vte-885.webp 885w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/AAT-n50vte-1180.webp 1180w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/AAT-n50vte-1475.webp 1475w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/AAT-n50vte-295.jpeg 295w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/AAT-n50vte-590.jpeg 590w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/AAT-n50vte-885.jpeg 885w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/AAT-n50vte-1180.jpeg 1180w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/AAT-n50vte-1475.jpeg 1475w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Pulumi resource graph&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/AAT-n50vte-295.jpeg&quot; width=&quot;1475&quot; height=&quot;711&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h2 id=&quot;creating-the-project&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/#creating-the-project&quot;&gt;Creating the project&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Start by creating the application, and adding the AzureAD package we&#39;ll need to create the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/active-directory/develop/app-objects-and-service-principals&quot;&gt;Azure AD application registration&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;pulumi new azure-csharp `&lt;br /&gt;  &lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;name easyauth-webapp `&lt;br /&gt;  &lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;description &lt;span class=&quot;token string&quot;&gt;&quot;azure ad secured app&quot;&lt;/span&gt; `&lt;br /&gt;  &lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;stack dev `&lt;br /&gt;  &lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;config azure-native:location=eastus&lt;br /&gt;&lt;br /&gt;dotnet add package Pulumi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AzureAD&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Note the example defaults to eastus - you can set this to whatever region you want, I&#39;ve included it here so the script will complete without any prompts.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We next need to update the contents of the &lt;code&gt;pulumi.dev.yaml&lt;/code&gt; file to contain a few additional config items. Paste the following into the file:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;azure-native:location&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; eastus&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;azure-native:subscriptionId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; UPDATE_ME&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;azure-native:tenantId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; UPDATE_ME&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;easyauth-webapp:tenantId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; UPDATE_ME&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;easyauth-webapp:ownerId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; UPDATE_ME&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;easyauth-webapp:siteName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; UPDATE_ME&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;easyauth-webapp:appRegistrationName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; UPDATE_ME&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can set &lt;code&gt;siteName&lt;/code&gt; and &lt;code&gt;appRegistrationName&lt;/code&gt; to whatever you want, though for simplicity&#39;s sake I&#39;d suggest using the same item. As an example I might pick &lt;code&gt;easy-auth-azure-ad&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;subscriptionId&lt;/code&gt; and &lt;code&gt;tenantId&lt;/code&gt; should be set to the appropriate target&#39;s for your Azure app service and Azure AD application registration, respectively. I recommend setting the &lt;code&gt;ownerId&lt;/code&gt; to your user&#39;s id, otherwise you may find you don&#39;t have the permission to modify or delete the application registration after it has been deployed.&lt;/p&gt;
&lt;p&gt;The following commands may be helpful in retrieving these values (requires the &lt;a href=&quot;https://docs.microsoft.com/en-us/cli/azure/&quot;&gt;Azure CLI&lt;/a&gt;):&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Get your user&#39;s id&lt;/span&gt;&lt;br /&gt;az ad signed-in-user show &lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;query objectId&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# List all subscriptions (and their tenant) that you have access to&lt;/span&gt;&lt;br /&gt;az account list&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Note &lt;code&gt;tenantId&lt;/code&gt; is set twice as I couldn&#39;t figure out how to access the &lt;code&gt;azure-native:tenantId&lt;/code&gt; via configuration, and it is needed both to set the default tenant for the application registration deployment, and to construct the token issuer URI.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;deploy-the-website-no-security&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/#deploy-the-website-no-security&quot;&gt;Deploy the website (no security)&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We&#39;ll next create the website we want to deploy. We&#39;re going to use the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/app-service/deploy-run-package&quot;&gt;run from ZIP package&lt;/a&gt; functionality to deploy the contents of the &lt;code&gt;wwwroot&lt;/code&gt; folder. Create that folder and add some content to the &lt;code&gt;index.htm&lt;/code&gt; file:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- wwwroot/index.htm --&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;A very secure app&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    Hello EasyAuth with Pulumi!&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can deploy this file to Azure with Pulumi. Modify the &lt;code&gt;MyStack.cs&lt;/code&gt; file to contain the below code, which has been adapted from the &lt;a href=&quot;https://github.com/pulumi/examples/blob/master/azure-cs-functions/FunctionsStack.cs&quot;&gt;Pulumi Function Stack example&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// MyStack.cs&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;Pulumi&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;Pulumi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AzureAD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;Pulumi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AzureAD&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Inputs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;Pulumi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AzureNative&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Resources&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;Pulumi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AzureNative&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Storage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;Pulumi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AzureNative&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Storage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Inputs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;Pulumi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AzureNative&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Web&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;Pulumi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AzureNative&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Web&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Inputs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MyStack&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Stack&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;MyStack&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; config &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;Pulumi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; tenantId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;tenantId&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; ownerId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ownerId&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; siteName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;siteName&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; appRegistrationName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;appRegistrationName&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; rg &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;ResourceGroup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;$&quot;RG-&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;siteName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; storageAccount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;StorageAccount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;storageaccount&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;StorageAccountArgs&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      ResourceGroupName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; rg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      Kind &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;StorageV2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      Sku &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;SkuArgs&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        Name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; SkuName&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Standard_LRS&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; appServicePlan &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;AppServicePlan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;appserviceplan&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;AppServicePlanArgs&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      ResourceGroupName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; rg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      Kind &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;App&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      Sku &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;SkuDescriptionArgs&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        Tier &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Basic&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        Name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;B1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; container &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;BlobContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;zips&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;BlobContainerArgs&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      AccountName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; storageAccount&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      PublicAccess &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; PublicAccess&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;None&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      ResourceGroupName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; rg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; blob &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;Blob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;appservice-blob&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;BlobArgs&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      ResourceGroupName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; rg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      AccountName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; storageAccount&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      ContainerName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      Type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; BlobType&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Block&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      Source &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;FileArchive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;wwwroot&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; codeBlobUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;SignedBlobReadUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;blob&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; container&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; storageAccount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; rg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; app &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;WebApp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;app&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;WebAppArgs&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      Name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; siteName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      ResourceGroupName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; rg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      ServerFarmId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; appServicePlan&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      SiteConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;SiteConfigArgs&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        AppSettings &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;NameValuePairArgs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;              Name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;WEBSITE_RUN_FROM_PACKAGE&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;              Value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; codeBlobUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Endpoint &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DefaultHostName&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// From https://github.com/pulumi/examples/blob/master/azure-cs-functions/FunctionsStack.cs&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Output&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;SignedBlobReadUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Blob&lt;/span&gt; blob&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BlobContainer&lt;/span&gt; container&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StorageAccount&lt;/span&gt; account&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ResourceGroup&lt;/span&gt; resourceGroup&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Output&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;Tuple&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;        blob&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; account&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; resourceGroup&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Apply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; blobName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; containerName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; accountName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; resourceGroupName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;      &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; blobSAS &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ListStorageAccountServiceSAS&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;InvokeAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;ListStorageAccountServiceSASArgs&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        AccountName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; accountName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        Protocols &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; HttpProtocol&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Https&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        SharedAccessStartTime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2021-01-01&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        SharedAccessExpiryTime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2030-01-01&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        Resource &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; SignedResource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;C&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        ResourceGroupName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; resourceGroupName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        Permissions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Permissions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;R&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        CanonicalizedResource &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/blob/&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; accountName &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; containerName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        ContentType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        CacheControl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;max-age=5&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        ContentDisposition &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;inline&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        ContentEncoding &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;deflate&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Output&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;$&quot;https://&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;accountName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.blob.core.windows.net/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;containerName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;blobName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;blobSAS&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ServiceSasToken&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Output&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Output&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; Endpoint &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can now deploy the site and verify it has worked as intended:&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;pulumi up &lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;stack dev&lt;br /&gt;&lt;br /&gt;curl &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pulumi stack &lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;stack dev output Endpoint&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/eOgQ3sCm2M-295.avif 295w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/eOgQ3sCm2M-590.avif 590w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/eOgQ3sCm2M-885.avif 885w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/eOgQ3sCm2M-1180.avif 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/eOgQ3sCm2M-295.webp 295w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/eOgQ3sCm2M-590.webp 590w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/eOgQ3sCm2M-885.webp 885w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/eOgQ3sCm2M-1180.webp 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/eOgQ3sCm2M-295.jpeg 295w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/eOgQ3sCm2M-590.jpeg 590w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/eOgQ3sCm2M-885.jpeg 885w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/eOgQ3sCm2M-1180.jpeg 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;HTML output from curl&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/eOgQ3sCm2M-295.jpeg&quot; width=&quot;1180&quot; height=&quot;375&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h2 id=&quot;securing-the-site&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/#securing-the-site&quot;&gt;Securing the site&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To configure Easy Auth we first create an Azure AD application registration. In this example I&#39;m specifying &lt;code&gt;AzureADMyOrg&lt;/code&gt; which restricts access to the tenant the application registration is deployed in. I&#39;m also adding a &lt;code&gt;RedirectUri&lt;/code&gt; that points at the Easy Auth middleware of the deployed site. A password is needed to use as a client secret (the web application being the client in this case).&lt;/p&gt;
&lt;p&gt;Once the application registration is created we can add &lt;a href=&quot;https://www.pulumi.com/docs/reference/pkg/azure-native/web/webappauthsettings/&quot;&gt;WebAppAuthSettings&lt;/a&gt; to our site. The example specifies no anonymous access (using &lt;code&gt;RedirectToLoginPage&lt;/code&gt;), and connects the site to the application registration using the &lt;code&gt;ClientId&lt;/code&gt; and &lt;code&gt;ClientSecret&lt;/code&gt; (password).&lt;/p&gt;
&lt;p&gt;Paste the below code just after the &lt;code&gt;this.Endpoint...&lt;/code&gt; code in &lt;code&gt;MyStack.cs&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// MyStack.cs&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// After this.Endpoint = app.DefaultHostName;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; adApp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;Application&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ADAppRegistration&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;ApplicationArgs&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  DisplayName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; appRegistrationName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  SignInAudience &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;AzureADMyOrg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  Owners &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ownerId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  Web &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;ApplicationWebArgs&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    ImplicitGrant &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;ApplicationWebImplicitGrantArgs&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      IdTokenIssuanceEnabled &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    RedirectUris &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;System&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Collections&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Generic&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;List&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;$&quot;https://&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;siteName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.azurewebsites.net/.auth/login/aad/callback&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; applicationPassword &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;ApplicationPassword&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;appPassword&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;ApplicationPasswordArgs&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  ApplicationObjectId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; adApp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  DisplayName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Client secret for web app&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; allowedAudience &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; adApp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ApplicationId&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Apply&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;$&quot;api://&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; authSettings &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;WebAppAuthSettings&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;authSettings&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;WebAppAuthSettingsArgs&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  ResourceGroupName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; rg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  Name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  Enabled &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  UnauthenticatedClientAction &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; UnauthenticatedClientAction&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;RedirectToLoginPage&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  DefaultProvider &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; BuiltInAuthenticationProvider&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AzureActiveDirectory&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  ClientId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; adApp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ApplicationId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  ClientSecret &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; applicationPassword&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  Issuer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;$&quot;https://sts.windows.net/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;tenantId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/v2.0&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  AllowedAudiences &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; allowedAudience &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can now update the site, and if we try to access the endpoint we&#39;ll notice it is no longer available over http. From the command line we can&#39;t get much further than this, but in a browser we&#39;ll get redirected to complete the login flow and access the site.&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;pulumi up &lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;stack dev&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# Redirect to HTTPS&lt;/span&gt;&lt;br /&gt;curl &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pulumi stack &lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;stack dev output Endpoint&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# Access denied&lt;/span&gt;&lt;br /&gt;curl &lt;span class=&quot;token string&quot;&gt;&quot;https://&lt;span class=&quot;token function&quot;&gt;$&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pulumi stack &lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;stack dev output Endpoint&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/oOtf3shKco-295.avif 295w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/oOtf3shKco-590.avif 590w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/oOtf3shKco-885.avif 885w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/oOtf3shKco-1180.avif 1180w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/oOtf3shKco-1475.avif 1475w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/oOtf3shKco-295.webp 295w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/oOtf3shKco-590.webp 590w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/oOtf3shKco-885.webp 885w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/oOtf3shKco-1180.webp 1180w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/oOtf3shKco-1475.webp 1475w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/oOtf3shKco-295.jpeg 295w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/oOtf3shKco-590.jpeg 590w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/oOtf3shKco-885.jpeg 885w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/oOtf3shKco-1180.jpeg 1180w, https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/oOtf3shKco-1475.jpeg 1475w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Access denied&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/oOtf3shKco-295.jpeg&quot; width=&quot;1475&quot; height=&quot;317&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h2 id=&quot;cleaning-up&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/#cleaning-up&quot;&gt;Cleaning up&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can remove all the resources with the following command:&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;pulumi destroy &lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;stack dev&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then remove the stack from the Pulumi console with:&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;pulumi stack &lt;span class=&quot;token function&quot;&gt;rm&lt;/span&gt; dev&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;notes&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/#notes&quot;&gt;Notes&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This example is using &lt;a href=&quot;https://www.pulumi.com/docs/reference/pkg/azure-native/web/webappauthsettings/&quot;&gt;WebAppAuthSettings&lt;/a&gt; rather than &lt;a href=&quot;https://www.pulumi.com/docs/reference/pkg/azure-native/web/webappauthsettingsv2/&quot;&gt;WebAppAuthSettingsV2&lt;/a&gt; due to &lt;a href=&quot;https://github.com/pulumi/pulumi-azure-native/issues/773&quot;&gt;a known bug&lt;/a&gt; that prevents it from working. Once this bug is fixed I recommend updating to v2, as the classic experience is due to be deprecated from the Azure portal.&lt;/p&gt;
&lt;p&gt;While building and debugging this I found the &lt;a href=&quot;https://developer.microsoft.com/en-us/graph/graph-explorer&quot;&gt;Microsoft Graph Explorer&lt;/a&gt; to be helpful. I managed to waste a good half hour until I realized the important distinction between the application registration&#39;s &lt;code&gt;object id&lt;/code&gt;, and the application registration&#39;s &lt;code&gt;application id&lt;/code&gt;. Both were needed in this example!&lt;/p&gt;
&lt;p&gt;Finally, an observation that it&#39;s fairly common to have the permission to create application registrations, but if you fail to specify yourself as an owner you won&#39;t be able to edit/delete it. If you&#39;re not developing in a test tenant you might need to speak to IT/Security to clean up some failed attempts (speaking from experience...).&lt;/p&gt;
</description>
      <pubDate>Sat, 23 Oct 2021 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2021/10/deploy-an-azure-ad-protected-app-service-website-with-pulumi/</guid>
    </item>
    <item>
      <title>Run Pester 5 tests in Azure Pipelines and publish test results</title>
      <link>https://tjaddison.com/blog/2021/09/run-pester-5-tests-in-azure-pipelines-and-publish-test-results/</link>
      <description>&lt;p&gt;The brevity of the &lt;a href=&quot;https://pester.dev/docs/migrations/v4-to-v5&quot;&gt;Migrating from Pester v4 to v5&lt;/a&gt; documentation belies just how much has changed - something that took a fair bit of trial and error for me was getting &lt;a href=&quot;https://pester.dev/&quot;&gt;Pester&lt;/a&gt; 5 test results published in an &lt;a href=&quot;https://azure.microsoft.com/en-us/services/devops/pipelines/&quot;&gt;Azure Pipelines&lt;/a&gt; run.&lt;/p&gt;
&lt;p&gt;Assuming you have your tests located in a &lt;code&gt;tests&lt;/code&gt; folder, the pipeline definition needed to run the tests and publish the results is below.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I could have used &lt;code&gt;Invoke-Pester -Path tests&lt;/code&gt;, but I frequently find tests which use relative paths, and so invoking them from the correct folder is easier.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# /pipelines/azure-pipelines.yml&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;pool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;vmImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; windows&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2019&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; PowerShell@2&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;displayName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Run Pester tests&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;targetType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;inline&quot;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;&lt;br /&gt;        Set-Location ./tests&lt;br /&gt;        Invoke-Pester -CI&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;ignoreLASTEXITCODE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; PublishTestResults@2&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;testResultsFormat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;NUnit&quot;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;testResultsFiles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;**/Test*.xml&quot;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;failTaskOnFailedTests&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;testRunTitle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Validate Task Files&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;-CI&lt;/code&gt; argument to &lt;code&gt;Invoke-Pester&lt;/code&gt; will save test results in an xml file (NUnitXML compatible) in the same folder. It will also set the exit code of the process to the number of failed tests, which is problematic as any non-zero exit code will abort the pipeline by default. To ensure our pipeline continues when tests fail, we set &lt;code&gt;ignoreLASTEXITCODE&lt;/code&gt; on the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/utility/powershell&quot;&gt;PowerShell task&lt;/a&gt;. To stop the pipeline in the presence of failed tasks we use the &lt;code&gt;failTaskOnFailedTests&lt;/code&gt; property of the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/test/publish-test-results&quot;&gt;Publish Test Results task&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In the example below, I&#39;m running the &lt;a href=&quot;https://github.com/DBTrenches/tsqlscheduler/blob/master/src/tsqlScheduler/Public/Test-FolderTasks.ps1&quot;&gt;Test-FolderTask&lt;/a&gt; function of &lt;a href=&quot;https://github.com/DBTrenches/tsqlscheduler&quot;&gt;tSqlScheduler&lt;/a&gt;, and as you can see when I break the build on purpose, the tests fail 😊.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2021/09/run-pester-5-tests-in-azure-pipelines-and-publish-test-results/5faA1Xx01y-295.avif 295w, https://tjaddison.com/blog/2021/09/run-pester-5-tests-in-azure-pipelines-and-publish-test-results/5faA1Xx01y-590.avif 590w, https://tjaddison.com/blog/2021/09/run-pester-5-tests-in-azure-pipelines-and-publish-test-results/5faA1Xx01y-885.avif 885w, https://tjaddison.com/blog/2021/09/run-pester-5-tests-in-azure-pipelines-and-publish-test-results/5faA1Xx01y-1180.avif 1180w, https://tjaddison.com/blog/2021/09/run-pester-5-tests-in-azure-pipelines-and-publish-test-results/5faA1Xx01y-1475.avif 1475w, https://tjaddison.com/blog/2021/09/run-pester-5-tests-in-azure-pipelines-and-publish-test-results/5faA1Xx01y-1770.avif 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2021/09/run-pester-5-tests-in-azure-pipelines-and-publish-test-results/5faA1Xx01y-295.webp 295w, https://tjaddison.com/blog/2021/09/run-pester-5-tests-in-azure-pipelines-and-publish-test-results/5faA1Xx01y-590.webp 590w, https://tjaddison.com/blog/2021/09/run-pester-5-tests-in-azure-pipelines-and-publish-test-results/5faA1Xx01y-885.webp 885w, https://tjaddison.com/blog/2021/09/run-pester-5-tests-in-azure-pipelines-and-publish-test-results/5faA1Xx01y-1180.webp 1180w, https://tjaddison.com/blog/2021/09/run-pester-5-tests-in-azure-pipelines-and-publish-test-results/5faA1Xx01y-1475.webp 1475w, https://tjaddison.com/blog/2021/09/run-pester-5-tests-in-azure-pipelines-and-publish-test-results/5faA1Xx01y-1770.webp 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2021/09/run-pester-5-tests-in-azure-pipelines-and-publish-test-results/5faA1Xx01y-295.jpeg 295w, https://tjaddison.com/blog/2021/09/run-pester-5-tests-in-azure-pipelines-and-publish-test-results/5faA1Xx01y-590.jpeg 590w, https://tjaddison.com/blog/2021/09/run-pester-5-tests-in-azure-pipelines-and-publish-test-results/5faA1Xx01y-885.jpeg 885w, https://tjaddison.com/blog/2021/09/run-pester-5-tests-in-azure-pipelines-and-publish-test-results/5faA1Xx01y-1180.jpeg 1180w, https://tjaddison.com/blog/2021/09/run-pester-5-tests-in-azure-pipelines-and-publish-test-results/5faA1Xx01y-1475.jpeg 1475w, https://tjaddison.com/blog/2021/09/run-pester-5-tests-in-azure-pipelines-and-publish-test-results/5faA1Xx01y-1770.jpeg 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Test Results&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2021/09/run-pester-5-tests-in-azure-pipelines-and-publish-test-results/5faA1Xx01y-295.jpeg&quot; width=&quot;1770&quot; height=&quot;934&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
</description>
      <pubDate>Thu, 30 Sep 2021 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2021/09/run-pester-5-tests-in-azure-pipelines-and-publish-test-results/</guid>
    </item>
    <item>
      <title>Interesting Links - August 2021</title>
      <link>https://tjaddison.com/blog/2021/08/interesting-links---august-2021/</link>
      <description>&lt;p&gt;With the advent of tab groups I can now claim to have &#39;only a few tabs&#39; open, yet have dozens (maybe more) links pile up for later review. I&#39;m not sure that&#39;s an improvement 🤔.&lt;/p&gt;
&lt;p&gt;Links are broadly categorised into &lt;a href=&quot;https://tjaddison.com/blog/2021/08/interesting-links---august-2021/#frontend&quot;&gt;Frontend&lt;/a&gt;, &lt;a href=&quot;https://tjaddison.com/blog/2021/08/interesting-links---august-2021/#development&quot;&gt;Development&lt;/a&gt;, &lt;a href=&quot;https://tjaddison.com/blog/2021/08/interesting-links---august-2021/#management-and-leadership&quot;&gt;Management and leadership&lt;/a&gt;, and the catch-all &lt;a href=&quot;https://tjaddison.com/blog/2021/08/interesting-links---august-2021/#miscellaneous&quot;&gt;Miscellaneous&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;frontend&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/08/interesting-links---august-2021/#frontend&quot;&gt;Frontend&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;webmention-analytics&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/08/interesting-links---august-2021/#webmention-analytics&quot;&gt;Webmention Analytics&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Add visibility to your site&#39;s webmention implementation with &lt;a href=&quot;https://mxb.dev/blog/webmention-analytics/&quot;&gt;Webmention Analytics&lt;/a&gt;. In addition to being something you can fork and deploy pretty quickly, it also demonstrates how you can build on top of your webmention data. Assumes you&#39;re using webmention.io.&lt;/p&gt;
&lt;h3 id=&quot;typescript-deep-dive&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/08/interesting-links---august-2021/#typescript-deep-dive&quot;&gt;TypeScript Deep Dive&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you prefer learning via reading (and the official &lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/intro.html&quot;&gt;Typescript Handbook&lt;/a&gt; wasn&#39;t your style), I found the &lt;a href=&quot;https://basarat.gitbook.io/typescript/&quot;&gt;TypeScript Deep Dive&lt;/a&gt; to be an accessible and much more practical introduction (includes topics like project setup, getting up and running with a React app). In addition to the basics, it includes real-world usage tips, a style guide, and a deep dive into the TypeScript compiler internals.&lt;/p&gt;
&lt;h3 id=&quot;dom-events&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/08/interesting-links---august-2021/#dom-events&quot;&gt;DOM Events&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://domevents.dev/&quot;&gt;DOM Events&lt;/a&gt; is a fantastic visual exposition of the browser&#39;s DOM event system that needs to be seen to be appreciated (click the &lt;em&gt;Dispatch&lt;/em&gt; button!). I can&#39;t think of a better way to teach/understand the model - a great UI that is both simple, informative, and yet feature-packed.&lt;/p&gt;
&lt;h3 id=&quot;react-from-scratch&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/08/interesting-links---august-2021/#react-from-scratch&quot;&gt;React from scratch&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This in-depth and enlightening three-part series (starting with &lt;a href=&quot;https://acko.net/blog/climbing-mt-effect/&quot;&gt;Climbing Mount Effect&lt;/a&gt;) lays out some of the problems React&#39;s architecture solves, and then extends that architecture with some very different solutions.&lt;/p&gt;
&lt;p&gt;If you&#39;ve never read the details on why some of React&#39;s limitations exist (one-way data flow, rules regarding hooks, effects) - this piece is well worth the effort to work through. And if you do understand those limitations, this piece shows another way of thinking about them - really helped consolidate my mental model of React.&lt;/p&gt;
&lt;p&gt;If you&#39;re curious about building React from scratch, I&#39;d recommend the fantastic &lt;a href=&quot;https://pomb.us/build-your-own-react/&quot;&gt;Build your own React&lt;/a&gt;. Not only is the content great, but the presentation is fantastic.&lt;/p&gt;
&lt;h3 id=&quot;javascript&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/08/interesting-links---august-2021/#javascript&quot;&gt;Javascript&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Looking forward to hopefully seeing the &lt;a href=&quot;https://github.com/tc39/proposal-json-modules&quot;&gt;proposal for JSON modules&lt;/a&gt; make it into the standard. I&#39;ve got a whole bunch of JSON being imported in various utility tools, and if it could fail-fast if it&#39;s no longer JSON (for any reason), so much the better.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; json &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./foo.json&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;json&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And now looking back, after reading &lt;a href=&quot;https://kentcdodds.com/blog/authentication-in-react-applications&quot;&gt;authentication in React apps&lt;/a&gt; it is hard to unsee the many times I have made the same mistakes over and over. At least next time I need to write an app I&#39;ve got a solid pattern to follow, and maybe one day I&#39;ll refactor some existing code into the pattern (hahaha...).&lt;/p&gt;
&lt;p&gt;Looking forward again, I&#39;m already confident that Next.js + Tailwindcss are my go-to choices for building. Hosting is a little trickier. While I&#39;ve been very happy with Netlify for static sites, because Next.js integrates so tightly with Vercel that&#39;s probably an upcoming change I&#39;ll make. Nothing has yet dislodged firebase/firestore as the go-to &lt;em&gt;web accessible&lt;/em&gt; data store to use...until I started reading about Supabase. And given it&#39;s actually SQL (hooray!) I&#39;m even &lt;em&gt;more&lt;/em&gt; interested than I was before. Reading through &lt;a href=&quot;https://www.freecodecamp.org/news/the-complete-guide-to-full-stack-development-with-supabas/&quot;&gt;an end to end tutorial&lt;/a&gt; has convinced me this is worth exploring more.&lt;/p&gt;
&lt;p&gt;Another missing piece of my puzzle is how to make calls to that backend - in most cases I&#39;m using a straightforward &lt;code&gt;useFirestore&lt;/code&gt; hook that has some home-rolled caching, but after reading through &lt;a href=&quot;https://tkdodo.eu/blog/practical-react-query&quot;&gt;practical react-query&lt;/a&gt; I&#39;m wondering if that might not be a better fit. I started the series with &lt;a href=&quot;https://tkdodo.eu/blog/react-query-as-a-state-manager&quot;&gt;react-query as a state manager&lt;/a&gt; and was hooked (no pun intended!), and promptly went back to the beginning to complete the series.&lt;/p&gt;
&lt;h2 id=&quot;development&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/08/interesting-links---august-2021/#development&quot;&gt;Development&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;local-first-software&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/08/interesting-links---august-2021/#local-first-software&quot;&gt;Local-first Software&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Data longevity is something I&#39;ve been thinking about a lot recently, especially when applied at a personal level (&lt;em&gt;can I leverage this data when I am 20, 30 years older?&lt;/em&gt;). The &lt;a href=&quot;https://www.inkandswitch.com/local-first.html&quot;&gt;Local-first software&lt;/a&gt; article covers this as well as 6 other concerns that point towards a very different kind of software.&lt;/p&gt;
&lt;p&gt;In addition to the principles, the &lt;a href=&quot;https://github.com/automerge/automerge&quot;&gt;Automerge&lt;/a&gt; library demonstrates that collaborative applications don&#39;t have to be incredibly challenging to implement (leave that to the library authors).&lt;/p&gt;
&lt;p&gt;I also noted &lt;a href=&quot;https://doc.replicache.dev/how-it-works&quot;&gt;Replicache&lt;/a&gt; that provides a hosted solution in the same space.&lt;/p&gt;
&lt;h3 id=&quot;data-longevity-datasets&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/08/interesting-links---august-2021/#data-longevity-datasets&quot;&gt;Data Longevity - Datasets&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;While thinking about data longevity I realized that I care far more about programmatic consumption of data, more than merely reading it - and so my focus is on datasets. The &lt;a href=&quot;https://www.loc.gov/preservation/resources/rfs/data.html&quot;&gt;US Library of Congress recommendation for datasets&lt;/a&gt; suggests:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Formats using well known schemas with public validation tool available&lt;/li&gt;
&lt;li&gt;Line-oriented, e.g. TSV, CSV, fixed-width&lt;/li&gt;
&lt;li&gt;Platform-independent open formats, e.g. .db, .db3&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I&#39;ve been focusing on &lt;a href=&quot;https://jsonlines.org/&quot;&gt;JSON Lines&lt;/a&gt; as my personal choice recently - the fact it&#39;s human readable is a huge plus, and it&#39;s also very close to the interchange format for most sites I&#39;m building (JSON round-trips well over the web!).&lt;/p&gt;
&lt;p&gt;However, after reading (and frankly having my mind blown) &lt;a href=&quot;https://phiresky.github.io/blog/2021/hosting-sqlite-databases-on-github-pages/&quot;&gt;hosting SQLite databases on GitHub pages&lt;/a&gt; I might reconsider using it instead of &#39;JSON databases&#39;.&lt;/p&gt;
&lt;h3 id=&quot;environment-setup&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/08/interesting-links---august-2021/#environment-setup&quot;&gt;Environment setup&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;One rough edge I&#39;ve found with codespaces so far is environment setup - and the docs for &lt;a href=&quot;https://docs.github.com/en/codespaces/customizing-your-codespace/&quot;&gt;customizing your codespace&lt;/a&gt; sent me down the rabbit hole of environment configuration.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/renemarc/dotfiles&quot;&gt;René-Marc&#39;s dotfiles&lt;/a&gt; were my jumping-off point, and in addition to discovering &lt;a href=&quot;https://scoop.sh/&quot;&gt;scoop&lt;/a&gt; I also found the rather formidable &lt;a href=&quot;https://www.chezmoi.io/&quot;&gt;chezmoi&lt;/a&gt;. A &lt;em&gt;lot&lt;/em&gt; to digest here (even the &lt;a href=&quot;https://www.chezmoi.io/docs/how-to/#personalizing-codespaces-for-your-account&quot;&gt;how-to&lt;/a&gt; is huge!), but it does look like the comprehensive solution that will let me work between Windows and macOS and linux.&lt;/p&gt;
&lt;p&gt;But there&#39;s a lot of work to do to get there...&lt;/p&gt;
&lt;h3 id=&quot;security-beyond-on-premise&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/08/interesting-links---august-2021/#security-beyond-on-premise&quot;&gt;Security beyond on-premise&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;While the tier model worked well for on-premise networks, it wasn&#39;t a good fit for a hybrid or cloud-native org (or basically any environment where you need to have &lt;a href=&quot;https://docs.microsoft.com/en-us/security/zero-trust/&quot;&gt;Zero Trust&lt;/a&gt;). If you&#39;re familiar with the tier model then the &lt;a href=&quot;https://docs.microsoft.com/en-us/security/compass/privileged-access-access-model&quot;&gt;Privileged Access Model&lt;/a&gt; provides a mapping from old to new.&lt;/p&gt;
&lt;h3 id=&quot;azure&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/08/interesting-links---august-2021/#azure&quot;&gt;Azure&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The discussion of &lt;a href=&quot;https://burkeholland.github.io/posts/the-urlist/&quot;&gt;how the Urlist app is built&lt;/a&gt; covers a lot of ground, and along the way hits Front Door, Cosmos DB, CNAME flattening, Azure Functions and more. It&#39;s eminently practical and also covers cost (which is never present on architecture diagrams), and left me with a better understanding of how some of the different tools fit together.&lt;/p&gt;
&lt;p&gt;When deploying all of those apps to Azure, perhaps rather than inflicting &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/overview&quot;&gt;ARM templates&lt;/a&gt; on yourself you could instead use the new &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview&quot;&gt;Bicep language&lt;/a&gt;. It&#39;s an improvement, but I don&#39;t know if it&#39;s enough of an improvement over something like &lt;a href=&quot;https://www.pulumi.com/&quot;&gt;Pulumi&lt;/a&gt; to be worth learning.&lt;/p&gt;
&lt;p&gt;I&#39;ve still not really leveraged durable functions, but found the &lt;a href=&quot;https://techcommunity.microsoft.com/t5/apps-on-azure/new-storage-providers-for-azure-durable-functions/ba-p/2382044&quot;&gt;new backend announcement&lt;/a&gt; to be particularly interesting. In particular for the &lt;a href=&quot;https://microsoft.github.io/durabletask-mssql/&quot;&gt;SQL backend&lt;/a&gt; I was curious how they implemented the workflow schema, as I&#39;ve had some painful experiences with workflow/orchestration inside of SQL Server before; one of the notes at the bottom confirmed what I had seen before:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In many cases, the database will be the primary performance bottleneck.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But with the option to use the &lt;a href=&quot;https://microsoft.github.io/durabletask-netherite/&quot;&gt;Netherite provider&lt;/a&gt; there is a faster, cheaper (albeit more setup required) option. The &lt;a href=&quot;https://github.com/Microsoft/FASTER&quot;&gt;FASTER&lt;/a&gt; technology that is used by Netherite is...&lt;em&gt;fast&lt;/em&gt;!&lt;/p&gt;
&lt;p&gt;And finally - tying together Security and durable functions is &lt;a href=&quot;https://www.microsoft.com/security/blog/2021/08/19/automating-security-assessments-using-cloud-katana/&quot;&gt;Cloud Katana&lt;/a&gt;. Love the work Microsoft security are doing to let people improve and iterate on security &lt;em&gt;safely&lt;/em&gt; (knowing where to start is hard, this is a fantastic onramp).&lt;/p&gt;
&lt;h3 id=&quot;access-control-explained&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/08/interesting-links---august-2021/#access-control-explained&quot;&gt;Access control, explained&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://tailscale.com/blog/rbac-like-it-was-meant-to-be/&quot;&gt;RBAC like it was meant to be&lt;/a&gt; is the best introduction to access control I&#39;ve ever read. &lt;a href=&quot;https://tailscale.com/&quot;&gt;Tailscale&lt;/a&gt; is an extremely impressive product, and the documentation/blog posts are just as impressive.&lt;/p&gt;
&lt;h2 id=&quot;management-and-leadership&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/08/interesting-links---august-2021/#management-and-leadership&quot;&gt;Management and Leadership&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;growing-inclusive-behaviours&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/08/interesting-links---august-2021/#growing-inclusive-behaviours&quot;&gt;Growing Inclusive Behaviours&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;One way to start is defining those behaviours, and Chelsea Troy&#39;s &lt;a href=&quot;https://chelseatroy.com/2018/05/24/why-your-efforts-to-make-your-company-inclusive-arent-working/&quot;&gt;Rubric for Evaluating Team Member&#39;s Contributions to an Inclusive Culture&lt;/a&gt; is still my go-to post/resource in this space. Highly recommended for everyone (not just managers).&lt;/p&gt;
&lt;h3 id=&quot;up-next-for-organisations&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/08/interesting-links---august-2021/#up-next-for-organisations&quot;&gt;Up Next for organisations?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;While everybody is thinking about more immediate changes related to remote work and the &#39;new-normal&#39;, what longer-term (think 2031) changes are already happening? &lt;a href=&quot;https://swardley.medium.com/how-organisations-are-changing-cf80f3e2300&quot;&gt;How organisations are changing&lt;/a&gt; covers a lot of ground (and remote vs. in-office is fairly pivotal question that is covered).&lt;/p&gt;
&lt;h3 id=&quot;engineering-career-growth&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/08/interesting-links---august-2021/#engineering-career-growth&quot;&gt;Engineering career growth&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Looking at something like Dropbox&#39;s &lt;a href=&quot;https://dropbox.github.io/dbx-career-framework/&quot;&gt;career framework&lt;/a&gt; you could be fooled into thinking that if you could just &lt;em&gt;define&lt;/em&gt; the ladder (Dropbox carefully don&#39;t call it a ladder, but it is) then you&#39;re half-way towards solving &#39;growth&#39;. The ladder framing (level 1...N) is common, and I&#39;m now wondering if it may be harmful when it comes to what I call terminal levels (the combination of individual/company/team means growth is not possible &lt;em&gt;or&lt;/em&gt; required).&lt;/p&gt;
&lt;p&gt;The specific problem I have with the ladder metaphor is that nobody climbs halfway up a ladder and stops - everyone wants to get to the top/get off the ladder. This can lead to conversations with a senior engineer (who will never make staff) and the discussion can end up fixating on &#39;why not staff&#39;. How can we instead focus on framing &#39;continued excellence as a senior engineer&#39;?&lt;/p&gt;
&lt;p&gt;Acknowledging the changing requirements of a role is important (if you level as senior now, invest nothing in personal development, would you still level as a senior in 5 years? 10 years?), and perhaps that is a way to frame a conversation about sustaining excellence. This isn&#39;t purely technical either - reading through &lt;a href=&quot;https://leaddev.com/professional-development/career-development-engineering-managers&quot;&gt;career development for engineering managers&lt;/a&gt;, how many of those points were you considering 5/10+ years ago? &lt;a href=&quot;https://twitter.com/GergelyOrosz/status/1427960129320804358&quot;&gt;Gergely&#39;s tweet&lt;/a&gt; hits a similar note - and I&#39;d say what makes those EM&#39;s unique is the ability to stay executing in the top N% for that role.&lt;/p&gt;
&lt;p&gt;One thing I&#39;ve found valuable in defining the senior+ roles is it allows you to identify projects/behaviours you would like to see other roles (either adjacent like EMs, or more junior for the same role) execute. The list of those is long, here are a few I&#39;ve enjoyed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.elidedbranches.com/2021/06/an-incomplete-list-of-skills-senior.html&quot;&gt;An incomplete list of skills senior engineers need beyond coding&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.elidedbranches.com/2020/11/driving-cultural-change-through.html&quot;&gt;Driving cultural change through software choices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://leaddev.com/scaling-teams-hypergrowth/driving-technical-strategy-scale-part-1&quot;&gt;Driving a technical strategy at scale&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://leaddev.com/productivity-eng-velocity/debugging-engineering-velocity-and-leading-high-performing-teams&quot;&gt;Debugging engineering velocity and leading high performance teams&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Even if you don&#39;t want to make these activities your focus (EM/staff engineer), making participation in them part of your role/though process may be part of how you sustain excellence.&lt;/p&gt;
&lt;h2 id=&quot;miscellaneous&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/08/interesting-links---august-2021/#miscellaneous&quot;&gt;Miscellaneous&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;orbit&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/08/interesting-links---august-2021/#orbit&quot;&gt;Orbit&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Spaced repetition (think Anki) taken to the next level. What if we could get much, much more from reading, by changing the format in which we author and then consume the text? See &lt;a href=&quot;https://numinous.productions/ttft/&quot;&gt;Tools for Transformative Thought&lt;/a&gt; for the background, &lt;a href=&quot;https://withorbit.com/&quot;&gt;Orbit&lt;/a&gt; for an introduction, or jump right into the &lt;a href=&quot;https://docs.withorbit.com/&quot;&gt;Orbit documentation&lt;/a&gt; for implementation details. This is no less than an attempt to reimagine the humble written word as a tool for learning, and I think it&#39;s worth keeping an eye on.&lt;/p&gt;
&lt;h3 id=&quot;my-debugging-hero&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/08/interesting-links---august-2021/#my-debugging-hero&quot;&gt;My debugging hero&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I didn&#39;t think I &lt;em&gt;had&lt;/em&gt; a debugging hero, but then I read another of Bruce Dawson&#39;s posts (this time on &lt;a href=&quot;https://randomascii.wordpress.com/2021/02/16/arranging-invisible-icons-in-quadratic-time/&quot;&gt;Arranging Invisible Icons in Quadractic time&lt;/a&gt;) and then realised that actually, I do!&lt;/p&gt;
&lt;h3 id=&quot;agency&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/08/interesting-links---august-2021/#agency&quot;&gt;Agency&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As a (relatively) freshly minted parent I&#39;m now starting to think about education, and reflecting back on my experiences I can say that I&#39;d like to do better. Reading &lt;a href=&quot;https://simonsarris.substack.com/p/the-most-precious-resource-is-agency&quot;&gt;The most precious resource is agency&lt;/a&gt; struck a chord, mainly in that outside of the social value of school, I&#39;m not sure how valuable our current system is compared to what it could be.&lt;/p&gt;
</description>
      <pubDate>Tue, 31 Aug 2021 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2021/08/interesting-links---august-2021/</guid>
    </item>
    <item>
      <title>Benchmarking different versions of NuGet packages</title>
      <link>https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/</link>
      <description>&lt;p&gt;Last month I looked at &lt;a href=&quot;https://tjaddison.com/blog/2021/06/run-benchmarks-using-multiple-runtimes-with-benchmarkdotnet/&quot;&gt;benchmarking different runtimes&lt;/a&gt; to see what impact updating to the latest version of .NET might have. But what if you&#39;re curious about package updates too? &lt;a href=&quot;https://benchmarkdotnet.org/&quot;&gt;BenchmarkDotNet&lt;/a&gt; has us covered there too - it allows you to configure your benchmarks to run against &lt;a href=&quot;https://benchmarkdotnet.org/articles/samples/IntroNuGet.html&quot;&gt;multiple versions of the same package&lt;/a&gt;. We can also leverage that functionality to benchmark packages that provide different implementations of the same abstract class or interface (e.g. &lt;code&gt;DbConnection&lt;/code&gt; as implemented in &lt;code&gt;Microsoft.Data.SqlClient&lt;/code&gt; and &lt;code&gt;System.Data.SqlClient&lt;/code&gt;).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you&#39;d like to jump right into an example project with all the bells and whistles check the &lt;a href=&quot;https://github.com/taddison/dotnet-sql-benchmarks/tree/main/src/SqlClientUpdate&quot;&gt;SqlClientUpdate benchmark on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The rest of this post will walk through:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Benchmarking two different versions of a package&lt;/li&gt;
&lt;li&gt;Benchmarking different sets of package&lt;/li&gt;
&lt;li&gt;Benchmarking different packages that implement a common abstract class&lt;/li&gt;
&lt;li&gt;Benchmarking all of the above with different runtimes&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;benchmarking-two-different-versions-of-a-package&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/#benchmarking-two-different-versions-of-a-package&quot;&gt;Benchmarking two different versions of a package&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We&#39;ll start by quickly scaffolding an app and adding a benchmark for connecting to SQL Server with &lt;a href=&quot;https://www.nuget.org/packages/System.Data.SqlClient/&quot;&gt;System.Data.SqlClient&lt;/a&gt; and &lt;a href=&quot;https://www.nuget.org/packages/Dapper&quot;&gt;Dapper&lt;/a&gt;. First of all create the app and install the packages - we&#39;re going to be looking at changes in data access, so we&#39;ll install versions of those packages that correspond to March 2019:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;dotnet new console&lt;br /&gt;dotnet &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; package BenchmarkDotNet&lt;br /&gt;dotnet &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; package Dapper &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1.60&lt;/span&gt;.6&lt;br /&gt;dotnet &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; package System.Data.SqlClient &lt;span class=&quot;token parameter variable&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4.6&lt;/span&gt;.0&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;It&#39;s important to install the lowest version of the package you want to benchmark, as if you install the latest version any attempt to restore a lower version will fail.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Next we&#39;ll modify our &lt;code&gt;Program.cs&lt;/code&gt; to use &lt;code&gt;BenchmarkSwitcher&lt;/code&gt; to launch our benchmarks:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Program.cs&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;BenchmarkDotNet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Running&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Program&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;&lt;br /&gt;    BenchmarkSwitcher&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;FromAssemblies&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token type-expression class-name&quot;&gt;Program&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Assembly &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And add a very minimal benchmark implementation to &lt;code&gt;SqlClientBenchmark.cs&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// SqlClientBenchmark.cs&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;BenchmarkDotNet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Attributes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;Dapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SqlClientBenchmark&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; CONNECTION_STRING &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;server=localhost;integrated security=sspi&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;System&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Common&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DbConnection&lt;/span&gt; _connection&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;GlobalSetup&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Setup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; _connection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;System&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SqlClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SqlConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;CONNECTION_STRING&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;GlobalCleanup&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Cleanup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; _connection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Dispose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Benchmark&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; _connection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;SELECT 1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we wanted to see what the impact of upgrading &lt;code&gt;System.Data.SqlClient&lt;/code&gt; to the latest version would be, we need to use a &lt;a href=&quot;https://benchmarkdotnet.org/articles/configs/configs.html&quot;&gt;Config&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// SqlClientBenchmark.cs&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;BenchmarkDotNet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Attributes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;BenchmarkDotNet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Configs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;BenchmarkDotNet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;Dapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Config&lt;/span&gt;&lt;span class=&quot;token attribute-arguments&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token type-expression class-name&quot;&gt;Config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SqlClientBenchmark&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Config&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;ManualConfig&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; baseJob &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Job&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Default&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;AddJob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;baseJob&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WithNuGet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;System.Data.SqlClient&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;4.6.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;AddJob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;baseJob&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WithNuGet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;System.Data.SqlClient&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;4.8.2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we run the benchmark we&#39;ll now get one run for each version of &lt;code&gt;System.Data.SqlClient&lt;/code&gt; specified.&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;dotnet run &lt;span class=&quot;token parameter variable&quot;&gt;-c&lt;/span&gt; Release &lt;span class=&quot;token parameter variable&quot;&gt;--filter&lt;/span&gt; * &lt;span class=&quot;token parameter variable&quot;&gt;--join&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/_mAHRDwter-295.avif 295w, https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/_mAHRDwter-590.avif 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/_mAHRDwter-295.webp 295w, https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/_mAHRDwter-590.webp 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/_mAHRDwter-295.jpeg 295w, https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/_mAHRDwter-590.jpeg 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Two versions of a single package&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/_mAHRDwter-295.jpeg&quot; width=&quot;590&quot; height=&quot;193&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h2 id=&quot;benchmarking-two-different-sets-of-package-versions&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/#benchmarking-two-different-sets-of-package-versions&quot;&gt;Benchmarking two different sets of package versions&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We can also pass a list of packages, so our code in &lt;code&gt;Config&lt;/code&gt; becomes:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token function&quot;&gt;AddJob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;baseJob&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WithNuGet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;NuGetReferenceList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;NuGetReference&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;System.Data.SqlClient&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;4.6.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;NuGetReference&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Dapper&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1.60.6&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;AddJob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;baseJob&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WithNuGet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;NuGetReferenceList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;NuGetReference&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;System.Data.SqlClient&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;4.8.2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;NuGetReference&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Dapper&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2.0.90&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The benchmark will now compare the March 2019 versions against the July 2021 versions of both &lt;code&gt;Dapper&lt;/code&gt; and &lt;code&gt;System.Data.SqlClient&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/l0fZFl2Klg-295.avif 295w, https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/l0fZFl2Klg-590.avif 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/l0fZFl2Klg-295.webp 295w, https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/l0fZFl2Klg-590.webp 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/l0fZFl2Klg-295.jpeg 295w, https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/l0fZFl2Klg-590.jpeg 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Benchmark comparing multiple packages&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/l0fZFl2Klg-295.jpeg&quot; width=&quot;590&quot; height=&quot;182&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h2 id=&quot;benchmarking-different-packages-that-implement-a-common-abstract-class&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/#benchmarking-different-packages-that-implement-a-common-abstract-class&quot;&gt;Benchmarking different packages that implement a common abstract class&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We&#39;ll now consider what would happen if rather than upgrading to the latest version of &lt;code&gt;System.Data.SqlClient&lt;/code&gt;, we instead upgraded to the latest version of &lt;code&gt;Microsoft.Data.SqlClient&lt;/code&gt;. We&#39;ll create a single abstract class &lt;code&gt;BaseBenchmark&lt;/code&gt; which will hold all the benchmark definitions, and then two classes which implement &lt;code&gt;BaseBenchmark&lt;/code&gt;, one for each of the &lt;code&gt;*.SqlClient&lt;/code&gt; packages we want to test.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You don&#39;t have to use abstract classes and multiple implementations, and if you&#39;re only benchmarking a single method it is probably overkill (you might want to consider clipboard inheritance instead 😊).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Our base class contains all common code:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// BaseBenchmark.cs&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;BenchmarkDotNet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Attributes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;BenchmarkDotNet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;Dapper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;abstract&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BaseBenchmark&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; CONNECTION_STRING &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;server=localhost;integrated security=sspi&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Job&lt;/span&gt; BaseJob &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Job&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Default&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;System&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Common&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DbConnection&lt;/span&gt; _connection&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;GlobalCleanup&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Cleanup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; _connection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Dispose&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Benchmark&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; _connection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;SELECT 1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And each implementation contains a different &lt;code&gt;Config&lt;/code&gt;, and connection initialisation:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// SystemDataBenchmark.cs&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;BenchmarkDotNet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Attributes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;BenchmarkDotNet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Configs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;BenchmarkDotNet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Config&lt;/span&gt;&lt;span class=&quot;token attribute-arguments&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token type-expression class-name&quot;&gt;Config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SystemDataBenchmark&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;BaseBenchmark&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Config&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;ManualConfig&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token function&quot;&gt;AddJob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BaseJob&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WithNuGet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;NuGetReferenceList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;NuGetReference&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;System.Data.SqlClient&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;4.6.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;NuGetReference&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Dapper&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1.60.6&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;      &lt;span class=&quot;token function&quot;&gt;AddJob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BaseJob&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WithNuGet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;NuGetReferenceList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;NuGetReference&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;System.Data.SqlClient&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;4.8.2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;NuGetReference&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Dapper&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2.0.90&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;GlobalSetup&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Setup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; _connection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;System&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SqlClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SqlConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;CONNECTION_STRING&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// MicrosoftDataBenchmark.cs&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;BenchmarkDotNet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Attributes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;BenchmarkDotNet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Configs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;BenchmarkDotNet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Config&lt;/span&gt;&lt;span class=&quot;token attribute-arguments&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token type-expression class-name&quot;&gt;Config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MicrosoftDataBenchmark&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;BaseBenchmark&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Config&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token type-list&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;ManualConfig&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token function&quot;&gt;AddJob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BaseJob&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WithNuGet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;NuGetReferenceList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;NuGetReference&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Microsoft.Data.SqlClient&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;3.0.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;NuGetReference&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Dapper&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2.0.90&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;GlobalSetup&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Setup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; _connection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;Microsoft&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SqlClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SqlConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;CONNECTION_STRING&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&#39;ll also need to install the new NuGet package:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;dotnet &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; package Microsoft.Data.SqlClient&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running the benchmark now compares all three options (baseline, &lt;code&gt;System.Data&lt;/code&gt; upgrade, &lt;code&gt;Microsoft.Data&lt;/code&gt; upgrade):&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/RTZ4JNBTsr-295.avif 295w, https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/RTZ4JNBTsr-590.avif 590w, https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/RTZ4JNBTsr-885.avif 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/RTZ4JNBTsr-295.webp 295w, https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/RTZ4JNBTsr-590.webp 590w, https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/RTZ4JNBTsr-885.webp 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/RTZ4JNBTsr-295.jpeg 295w, https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/RTZ4JNBTsr-590.jpeg 590w, https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/RTZ4JNBTsr-885.jpeg 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Microsoft Data and System Data benchmark results&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/RTZ4JNBTsr-295.jpeg&quot; width=&quot;885&quot; height=&quot;250&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h2 id=&quot;benchmarking-all-of-the-above-with-different-runtimes&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/#benchmarking-all-of-the-above-with-different-runtimes&quot;&gt;Benchmarking all of the above with different runtimes&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So what if we wanted to also see what impact updating from .NET Framework 4.8.2 to .NET 5.0 would have? We&#39;ve been running our previous benchmarks with .NET 5.0, but in early 2019 we&#39;d have been using .NET Framework 4.8.2. To see what difference that makes we&#39;ll update our &lt;code&gt;System.Data&lt;/code&gt; benchmarks to look like this:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// SystemDataBenchmarks.cs&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;BenchmarkDotNet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Environments&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; oldPackages &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; BaseJob&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WithNuGet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;NuGetReferenceList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;NuGetReference&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;System.Data.SqlClient&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;4.6.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;NuGetReference&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Dapper&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1.60.6&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;      &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; newPackages &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; BaseJob&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WithNuGet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;NuGetReferenceList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;NuGetReference&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;System.Data.SqlClient&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;4.8.2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;NuGetReference&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Dapper&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2.0.90&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;      &lt;span class=&quot;token function&quot;&gt;AddJob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;oldPackages&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WithRuntime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ClrRuntime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Net48&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token function&quot;&gt;AddJob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;oldPackages&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WithRuntime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;CoreRuntime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Core50&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token function&quot;&gt;AddJob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newPackages&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WithRuntime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ClrRuntime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Net48&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token function&quot;&gt;AddJob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newPackages&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WithRuntime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;CoreRuntime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Core50&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will compare the old packages on both runtimes, as well as the new packages on both runtimes. We&#39;ll also try both runtimes for the &lt;code&gt;Microsoft.Data&lt;/code&gt; package update:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// MicrosoftDataBenchmarks.cs&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; packages &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; BaseJob&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WithNuGet&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;NuGetReferenceList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;NuGetReference&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Microsoft.Data.SqlClient&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;3.0.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;NuGetReference&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Dapper&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2.0.90&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;      &lt;span class=&quot;token function&quot;&gt;AddJob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;packages&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WithRuntime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ClrRuntime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Net48&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token function&quot;&gt;AddJob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;packages&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WithRuntime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;CoreRuntime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Core50&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&#39;ll also need to update the &lt;code&gt;csproj&lt;/code&gt; file to allow us to target multiple frameworks:&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; &amp;lt;TargetFramework&gt;net50&amp;lt;/TargetFramework&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; &amp;lt;TargetFrameworks&gt;net48;net5.0&amp;lt;/TargetFrameworks&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And invoking the benchmark now requires us to specify which framework we want to execute the host process with:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;dotnet run &lt;span class=&quot;token parameter variable&quot;&gt;-c&lt;/span&gt; Release &lt;span class=&quot;token parameter variable&quot;&gt;-f&lt;/span&gt; net48 &lt;span class=&quot;token parameter variable&quot;&gt;--filter&lt;/span&gt; * &lt;span class=&quot;token parameter variable&quot;&gt;--join&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The result of which is six different benchmarks (and so if we had a lot of methods to benchmark we might be here for quite a while):&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/KXXps8QsqV-295.avif 295w, https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/KXXps8QsqV-590.avif 590w, https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/KXXps8QsqV-885.avif 885w, https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/KXXps8QsqV-1180.avif 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/KXXps8QsqV-295.webp 295w, https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/KXXps8QsqV-590.webp 590w, https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/KXXps8QsqV-885.webp 885w, https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/KXXps8QsqV-1180.webp 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/KXXps8QsqV-295.jpeg 295w, https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/KXXps8QsqV-590.jpeg 590w, https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/KXXps8QsqV-885.jpeg 885w, https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/KXXps8QsqV-1180.jpeg 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Benchmark results with multiple runtimes&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/KXXps8QsqV-295.jpeg&quot; width=&quot;1180&quot; height=&quot;346&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;If you&#39;d like to see an example that contains multiple benchmark definitions (still focusing on &lt;code&gt;SqlClient&lt;/code&gt;) I&#39;d encourage you to check out the &lt;a href=&quot;https://github.com/taddison/dotnet-sql-benchmarks/tree/main/src/SqlClientUpdate&quot;&gt;SqlClientUpdate benchmark on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;All the benchmark results here are to demonstrate the techniques only - don&#39;t take away anything from the results. If you are interested in benchmarking data access from SQL Server I&#39;d encourage you to use a longer duration (&lt;code&gt;Job.VeryLongRun&lt;/code&gt; is what I use), use a dedicated SQL machine rather than localhost, and monitor the SQL instance to ensure any slowdowns aren&#39;t on the SQL side. This is easier said than done!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;--&lt;/p&gt;
</description>
      <pubDate>Fri, 16 Jul 2021 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2021/07/benchmarking-different-versions-of-nuget-packages/</guid>
    </item>
    <item>
      <title>Run benchmarks using multiple runtimes with BenchmarkDotNet</title>
      <link>https://tjaddison.com/blog/2021/06/run-benchmarks-using-multiple-runtimes-with-benchmarkdotnet/</link>
      <description>&lt;p&gt;It&#39;s never been easier to run benchmarks against multiple runtimes with &lt;a href=&quot;https://benchmarkdotnet.org/&quot;&gt;BenchmarkDotNet&lt;/a&gt;. Maybe you&#39;re checking your performance improvements benefit all runtimes targeted by your library code (that may support multiple &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/standard/frameworks&quot;&gt;target frameworks&lt;/a&gt;). Maybe you&#39;re curious to see if the relentless march of runtime improvements will benefit some of your existing code? Whatever the reason, with a couple of lines of code you can generate benchmarks like this:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2021/06/run-benchmarks-using-multiple-runtimes-with-benchmarkdotnet/HO8O_zgIx9-295.avif 295w, https://tjaddison.com/blog/2021/06/run-benchmarks-using-multiple-runtimes-with-benchmarkdotnet/HO8O_zgIx9-590.avif 590w, https://tjaddison.com/blog/2021/06/run-benchmarks-using-multiple-runtimes-with-benchmarkdotnet/HO8O_zgIx9-885.avif 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2021/06/run-benchmarks-using-multiple-runtimes-with-benchmarkdotnet/HO8O_zgIx9-295.webp 295w, https://tjaddison.com/blog/2021/06/run-benchmarks-using-multiple-runtimes-with-benchmarkdotnet/HO8O_zgIx9-590.webp 590w, https://tjaddison.com/blog/2021/06/run-benchmarks-using-multiple-runtimes-with-benchmarkdotnet/HO8O_zgIx9-885.webp 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2021/06/run-benchmarks-using-multiple-runtimes-with-benchmarkdotnet/HO8O_zgIx9-295.jpeg 295w, https://tjaddison.com/blog/2021/06/run-benchmarks-using-multiple-runtimes-with-benchmarkdotnet/HO8O_zgIx9-590.jpeg 590w, https://tjaddison.com/blog/2021/06/run-benchmarks-using-multiple-runtimes-with-benchmarkdotnet/HO8O_zgIx9-885.jpeg 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;BenchmarkDotNet results&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2021/06/run-benchmarks-using-multiple-runtimes-with-benchmarkdotnet/HO8O_zgIx9-295.jpeg&quot; width=&quot;885&quot; height=&quot;453&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;I first saw this technique explained in the &lt;a href=&quot;https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-5/&quot;&gt;performance improvements in .NET 5 post&lt;/a&gt; which made it easier to understand than the &lt;a href=&quot;https://benchmarkdotnet.org/articles/configs/toolchains.html&quot;&gt;toolchains documentation&lt;/a&gt;. The rest of this post doesn&#39;t introduce anything not already covered in the post/docs, but might be easier to digest.&lt;/p&gt;
&lt;h2 id=&quot;setup&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/06/run-benchmarks-using-multiple-runtimes-with-benchmarkdotnet/#setup&quot;&gt;Setup&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We&#39;re going to compare the performance of &lt;code&gt;String.StartsWith&lt;/code&gt; to &lt;code&gt;Span.StartsWith&lt;/code&gt; under:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;.NET Framework 4.8 (&lt;a href=&quot;https://dotnet.microsoft.com/download/dotnet-framework/net48&quot;&gt;download developer pack&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;.NET Core 3.1 (&lt;a href=&quot;https://dotnet.microsoft.com/download/dotnet/3.1&quot;&gt;download SDK&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;.NET 5 (&lt;a href=&quot;https://dotnet.microsoft.com/download/dotnet/5.0&quot;&gt;download SDK&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We&#39;ll then create a new console app for our benchmarks:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;dotnet new console &lt;span class=&quot;token parameter variable&quot;&gt;--name&lt;/span&gt; spanBench&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This bootstraps a new console application in the &lt;code&gt;spanBench&lt;/code&gt; folder.&lt;/p&gt;
&lt;h2 id=&quot;project-file&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/06/run-benchmarks-using-multiple-runtimes-with-benchmarkdotnet/#project-file&quot;&gt;Project File&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We&#39;re going to make three changes to our &lt;code&gt;spanBench.csproj&lt;/code&gt; file:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add the additional target frameworks (using the appropriate &lt;em&gt;Target Framework Moniker&lt;/em&gt; for each, see &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/standard/frameworks&quot;&gt;target frameworks&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Add the &lt;a href=&quot;https://benchmarkdotnet.org/&quot;&gt;BenchmarkDotNet&lt;/a&gt; dependency&lt;/li&gt;
&lt;li&gt;Conditionally add the &lt;a href=&quot;https://www.nuget.org/packages/System.Memory/&quot;&gt;System.Memory&lt;/a&gt; package when targeting .NET Framework 4.8 (the &lt;code&gt;System.Span&lt;/code&gt; type is included in the runtime from .NET Core 2.1)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Project&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;Sdk&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;Microsoft.NET.Sdk&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;PropertyGroup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;OutputType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Exe&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;OutputType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;TargetFrameworks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;net5.0;netcoreapp3.1;net48&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;TargetFrameworks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;PropertyGroup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;ItemGroup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;PackageReference&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;Include&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;BenchmarkDotNet&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;Version&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;0.13.0&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;ItemGroup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;ItemGroup&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;Condition&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#39;&lt;/span&gt;$(TargetFramework)&#39; == &#39;net48&#39; &lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;PackageReference&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;Include&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;System.Memory&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;Version&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;4.5.4&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;ItemGroup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;Project&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;benchmark-code&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/06/run-benchmarks-using-multiple-runtimes-with-benchmarkdotnet/#benchmark-code&quot;&gt;Benchmark Code&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The code is configured to use &lt;a href=&quot;https://benchmarkdotnet.org/articles/guides/how-to-run.html#benchmarkswitcher&quot;&gt;BenchmarkSwitcher&lt;/a&gt;, which allows you to specify which benchmarks to run via the command line, or interactively (if no &lt;code&gt;filter&lt;/code&gt; argument is passed). We&#39;ve also added the &lt;code&gt;MemoryDiagnoser&lt;/code&gt; attribute to collect information about allocations. The benchmarks are fairly simple and will each be executed with two different arguments.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;BenchmarkDotNet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Attributes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;BenchmarkDotNet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Running&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;BenchmarkDotNet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Diagnosers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;SpanPerf&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;MemoryDiagnoser&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Program&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt; args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; BenchmarkSwitcher&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;FromAssemblies&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token type-expression class-name&quot;&gt;Program&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Assembly &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Arguments&lt;/span&gt;&lt;span class=&quot;token attribute-arguments&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;http://www.google.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Arguments&lt;/span&gt;&lt;span class=&quot;token attribute-arguments&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://www.google.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Benchmark&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;bool&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;String_StartsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; str&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; str&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;StartsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Arguments&lt;/span&gt;&lt;span class=&quot;token attribute-arguments&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;http://www.google.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Arguments&lt;/span&gt;&lt;span class=&quot;token attribute-arguments&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://www.google.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;Benchmark&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;BotDetails&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Span_StartsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; str&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; str&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;AsSpan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;StartsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;https://&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;AsSpan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;running-the-benchmark&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/06/run-benchmarks-using-multiple-runtimes-with-benchmarkdotnet/#running-the-benchmark&quot;&gt;Running the benchmark&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With everything in place the below command will run all 12 benchmarks (2 methods x 2 arguments x 3 runtimes):&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;dotnet run &lt;span class=&quot;token parameter variable&quot;&gt;--configuration&lt;/span&gt; Release &lt;span class=&quot;token parameter variable&quot;&gt;--framework&lt;/span&gt; net48 &lt;span class=&quot;token parameter variable&quot;&gt;--runtimes&lt;/span&gt; net48 netcoreapp31 netcoreapp50 &lt;span class=&quot;token parameter variable&quot;&gt;--filter&lt;/span&gt; * &lt;span class=&quot;token parameter variable&quot;&gt;--join&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first two arguments are for the &lt;code&gt;dotnet run&lt;/code&gt; command:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use the release configuration (never benchmark with debug!)&lt;/li&gt;
&lt;li&gt;Build using the .NET Framework 4.8 surface area&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The final three arguments are forwarded to BenchmarkDotNet&#39;s &lt;code&gt;Run&lt;/code&gt; method:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;runtimes&lt;/code&gt; specifies the runtimes we want to benchmark with&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filter&lt;/code&gt; in this case specifies every benchmark in the assembly&lt;/li&gt;
&lt;li&gt;&lt;code&gt;join&lt;/code&gt; outputs all results at the end, rather than after each individual benchmark&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Tue, 08 Jun 2021 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2021/06/run-benchmarks-using-multiple-runtimes-with-benchmarkdotnet/</guid>
    </item>
    <item>
      <title>Publishing a self-contained single-file .NET 5 executable</title>
      <link>https://tjaddison.com/blog/2021/05/publishing-a-self-contained-single-file-net-5-executable/</link>
      <description>&lt;p&gt;Assuming you want to target Windows:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;dotnet publish &lt;span class=&quot;token parameter variable&quot;&gt;-c&lt;/span&gt; Release &lt;span class=&quot;token parameter variable&quot;&gt;-r&lt;/span&gt; win-x64 --self-contained &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-p:PublishSingleFile&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;true &lt;span class=&quot;token parameter variable&quot;&gt;-p:IncludeNativeLibrariesForSelfExtract&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;true&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And if you want to reduce the size (taking advantage of &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/core/deploying/trim-self-contained&quot;&gt;trimming&lt;/a&gt;):&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;dotnet publish &lt;span class=&quot;token parameter variable&quot;&gt;-c&lt;/span&gt; Release &lt;span class=&quot;token parameter variable&quot;&gt;-r&lt;/span&gt; win-x64 --self-contained &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-p:PublishSingleFile&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;true &lt;span class=&quot;token parameter variable&quot;&gt;-p:IncludeNativeLibrariesForSelfExtract&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;true &lt;span class=&quot;token parameter variable&quot;&gt;-p:PublishTrimmed&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;True &lt;span class=&quot;token parameter variable&quot;&gt;-p:TrimMode&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;Link&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you&#39;d like some background and an explanation of those options then read on.&lt;/p&gt;
&lt;h2 id=&quot;background-and-motivation&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/05/publishing-a-self-contained-single-file-net-5-executable/#background-and-motivation&quot;&gt;Background and Motivation&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I build a small benchmarking tool (&lt;a href=&quot;https://github.com/taddison/SQLDriver&quot;&gt;SQLDriver&lt;/a&gt;), and after updating to .NET 5 recently I realized that anyone using it may need to install an updated runtime. I&#39;d previously made an effort to keep the executable as easy to download and use as possible (using &lt;a href=&quot;https://github.com/dotnet/ILMerge&quot;&gt;ILMerge&lt;/a&gt; to bundle dependent assemblies into the executable), so wondered how easy things were with .NET 5?&lt;/p&gt;
&lt;h2 id=&quot;self-contained&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/05/publishing-a-self-contained-single-file-net-5-executable/#self-contained&quot;&gt;Self-Contained&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A self-contained .NET application is one that doesn&#39;t rely on there being any shared components on the target machine (such as the .NET runtime). This feature has actually been around for several years, and the only downside is that it can end up publishing a lot of additional files along with the app (which also means a simple console app can be 60MB+).&lt;/p&gt;
&lt;p&gt;Getting this to work requires a single flag:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;dotnet publish &lt;span class=&quot;token parameter variable&quot;&gt;-c&lt;/span&gt; Release &lt;span class=&quot;token parameter variable&quot;&gt;-r&lt;/span&gt; win-x64 --self-contained &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read more on the Microsoft docs site: &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/core/deploying/#publish-self-contained&quot;&gt;Self-contained publishing&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;single-file&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/05/publishing-a-self-contained-single-file-net-5-executable/#single-file&quot;&gt;Single-File&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Rather than dozens (or hundreds) of files, this option publishes only a handful of files, with the rest being unzipped into memory when the app is launched:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;dotnet publish &lt;span class=&quot;token parameter variable&quot;&gt;-c&lt;/span&gt; Release &lt;span class=&quot;token parameter variable&quot;&gt;-r&lt;/span&gt; win-x64 --self-contained &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-p:PublishSingleFile&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;true&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The reason there isn&#39;t a single file (which is what you might expect!) is that only managed DLLs are bundled into the executable, and the native binaries (part of the .NET runtime) are left as separate files. You can have the native binaries bundled by specifying the following:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;dotnet publish &lt;span class=&quot;token parameter variable&quot;&gt;-c&lt;/span&gt; Release &lt;span class=&quot;token parameter variable&quot;&gt;-r&lt;/span&gt; win-x64 --self-contained &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-p:PublishSingleFile&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;true &lt;span class=&quot;token parameter variable&quot;&gt;-p:IncludeNativeLibrariesForSelfExtract&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;true&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;In .NET Core 3.x the &lt;code&gt;PublishSingleFile&lt;/code&gt; option also bundled native binaries. The &lt;code&gt;IncludeNativeLibrariesForSelfExtract&lt;/code&gt; option is new wih .NET 5, where the default is to not bundle the native binaries.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Read more on the Microsoft docs site: &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/core/deploying/single-file&quot;&gt;Single-file deployment and executable&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;trimming&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/05/publishing-a-self-contained-single-file-net-5-executable/#trimming&quot;&gt;Trimming&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The final step for me was to enable &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/core/deploying/trim-self-contained&quot;&gt;trimming&lt;/a&gt;. Available only for self-contained apps, this feature allows bundling of only the assemblies that are used (rather than the entire .NET runtime):&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;dotnet publish &lt;span class=&quot;token parameter variable&quot;&gt;-c&lt;/span&gt; Release &lt;span class=&quot;token parameter variable&quot;&gt;-r&lt;/span&gt; win-x64 --self-contained &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-p:PublishSingleFile&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;true &lt;span class=&quot;token parameter variable&quot;&gt;-p:IncludeNativeLibrariesForSelfExtract&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;true &lt;span class=&quot;token parameter variable&quot;&gt;-p:PublishTrimmed&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;True&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can go one step further and remove unused code from assemblies (rather than whole assemblies only):&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;dotnet publish &lt;span class=&quot;token parameter variable&quot;&gt;-c&lt;/span&gt; Release &lt;span class=&quot;token parameter variable&quot;&gt;-r&lt;/span&gt; win-x64 --self-contained &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-p:PublishSingleFile&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;true &lt;span class=&quot;token parameter variable&quot;&gt;-p:IncludeNativeLibrariesForSelfExtract&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;true &lt;span class=&quot;token parameter variable&quot;&gt;-p:PublishTrimmed&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;True &lt;span class=&quot;token parameter variable&quot;&gt;-p:TrimMode&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;link&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The downside to trimming (including the more aggressive &lt;code&gt;link&lt;/code&gt; option) is that there are certain scenarios where the build-time analysis may incorrectly identify code as unused, which will result in a runtime error. The docs call out &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/core/deploying/trim-self-contained#components-that-cause-trimming-problems&quot;&gt;components that can cause trimming problems&lt;/a&gt; - and in my case the application was very simple and &lt;em&gt;also&lt;/em&gt; very easy to test for correctness.&lt;/p&gt;
&lt;p&gt;Read more on the Microsoft docs site: &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/core/deploying/trim-self-contained&quot;&gt;Trim self-contained deployments and executables&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/05/publishing-a-self-contained-single-file-net-5-executable/#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As a result of the self-contained, single-file, and trimming options I was able to get SQLDriver down to a single ~18MB executable that doesn&#39;t require anything to be installed on the target machine.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2021/05/publishing-a-self-contained-single-file-net-5-executable/9VjqxIXxOH-295.avif 295w, https://tjaddison.com/blog/2021/05/publishing-a-self-contained-single-file-net-5-executable/9VjqxIXxOH-590.avif 590w, https://tjaddison.com/blog/2021/05/publishing-a-self-contained-single-file-net-5-executable/9VjqxIXxOH-885.avif 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2021/05/publishing-a-self-contained-single-file-net-5-executable/9VjqxIXxOH-295.webp 295w, https://tjaddison.com/blog/2021/05/publishing-a-self-contained-single-file-net-5-executable/9VjqxIXxOH-590.webp 590w, https://tjaddison.com/blog/2021/05/publishing-a-self-contained-single-file-net-5-executable/9VjqxIXxOH-885.webp 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2021/05/publishing-a-self-contained-single-file-net-5-executable/9VjqxIXxOH-295.jpeg 295w, https://tjaddison.com/blog/2021/05/publishing-a-self-contained-single-file-net-5-executable/9VjqxIXxOH-590.jpeg 590w, https://tjaddison.com/blog/2021/05/publishing-a-self-contained-single-file-net-5-executable/9VjqxIXxOH-885.jpeg 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;SQLDriver build&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2021/05/publishing-a-self-contained-single-file-net-5-executable/9VjqxIXxOH-295.jpeg&quot; width=&quot;885&quot; height=&quot;395&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
</description>
      <pubDate>Sat, 29 May 2021 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2021/05/publishing-a-self-contained-single-file-net-5-executable/</guid>
    </item>
    <item>
      <title>Getting a summary of cspell errors with PowerShell</title>
      <link>https://tjaddison.com/blog/2021/04/getting-a-summary-of-cspell-errors-with-powershell/</link>
      <description>&lt;p&gt;After &lt;a href=&quot;https://tjaddison.com/blog/2021/02/spell-checking-your-markdown-blog-posts-with-cspell/&quot;&gt;configuring cspell for my blog&lt;/a&gt;, I was confronted with over 600 errors to work through. Looking through the list it was clear that in many cases I only needed to add some words to the &lt;code&gt;cspell.json&lt;/code&gt; workspace dictionary (though in some cases I just couldn&#39;t spell). Rather than going through each file and playing whack-a-mole I decided to aggregate all the errors and rank them:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2021/04/getting-a-summary-of-cspell-errors-with-powershell/tptttnoxEr-295.avif 295w, https://tjaddison.com/blog/2021/04/getting-a-summary-of-cspell-errors-with-powershell/tptttnoxEr-590.avif 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2021/04/getting-a-summary-of-cspell-errors-with-powershell/tptttnoxEr-295.webp 295w, https://tjaddison.com/blog/2021/04/getting-a-summary-of-cspell-errors-with-powershell/tptttnoxEr-590.webp 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2021/04/getting-a-summary-of-cspell-errors-with-powershell/tptttnoxEr-295.jpeg 295w, https://tjaddison.com/blog/2021/04/getting-a-summary-of-cspell-errors-with-powershell/tptttnoxEr-590.jpeg 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Error Summary&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2021/04/getting-a-summary-of-cspell-errors-with-powershell/tptttnoxEr-295.jpeg&quot; width=&quot;590&quot; height=&quot;231&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;This told me how many times the &#39;error&#39; showed up, and in how many files. My thinking here was that if the word was ubiquitous then I should probably add it to the &lt;code&gt;cspell.json&lt;/code&gt; file, and if it only showed up in one file then that file might be a better place.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is personal preference, and mainly driven by the fact that technology can be a bit of an alphabet soup of acronyms, and as I write fairly broadly a huge custom dictionary may let typos slip through. In cases where it was an English word I&#39;d always add it to the workspace dictionary, and in some cases where the word appeared in multiple files I&#39;d still opt for per-file overrides (if the word was a niche technical term that could look like a typo).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, how does the above command work?&lt;/p&gt;
&lt;h2 id=&quot;powershell-to-the-rescue&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/04/getting-a-summary-of-cspell-errors-with-powershell/#powershell-to-the-rescue&quot;&gt;PowerShell to the rescue&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We start by capturing the output of the &lt;code&gt;cspell&lt;/code&gt; command:&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$out&lt;/span&gt; = npx cspell content/&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;md*&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# $out Contains one row per error:&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# C:&#92;src&#92;blog&#92;content&#92;blog&#92;2020&#92;12&#92;analyze-your-create-react-app-bundle-size-without-ejecting&#92;index.md:82:70 - Unknown word (gatsbyjs)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# C:&#92;src&#92;blog&#92;content&#92;blog&#92;2017&#92;04&#92;checking-sql-agent-job-ownership-with-pester&#92;index.md:17:40 - Unknown word (msdb)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# ...snip...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can leverage the excellent &lt;a href=&quot;https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/convertfrom-string&quot;&gt;ConvertFrom-String&lt;/a&gt; command to pull the various items out by constructing a template:&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$template&lt;/span&gt; = @&lt;span class=&quot;token string&quot;&gt;&#39;&lt;br /&gt;{FileName*:C:&#92;src&#92;blog&#92;content&#92;blog&#92;2020&#92;12&#92;analyze-your-create-react-app-bundle-size-without-ejecting&#92;index.md}:82:70 - Unknown word ({SpellingError:gatsbyjs})&lt;br /&gt;{FileName*:C:&#92;src&#92;blog&#92;content&#92;blog&#92;2017&#92;04&#92;checking-sql-agent-job-ownership-with-pester&#92;index.md}:17:40 - Unknown word ({SpellingError:msdb})&lt;br /&gt;&#39;&lt;/span&gt;@&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&#39;ve specified that the &lt;code&gt;FileName&lt;/code&gt; appears multiple times with an asterisk (&lt;code&gt;FileName*&lt;/code&gt;), which generates a record for every occurrence of &lt;code&gt;FileName&lt;/code&gt; and it&#39;s associated &lt;code&gt;SpellingError&lt;/code&gt;. Passing this template to &lt;code&gt;ConvertFrom-String&lt;/code&gt; over our output gives us:&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$out&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ConvertFrom-String&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;TemplateContent &lt;span class=&quot;token variable&quot;&gt;$template&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# FileName      : C:&#92;src&#92;blog&#92;content&#92;blog&#92;2020&#92;12&#92;analyze-your-create-react-app-bundle-size-without-ejecting&#92;index.md&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# SpellingError : gatsbyjs&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# RunspaceId    : 8bc74548-2f8c-4f2b-b350-5f2d5c2dc033&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# FileName      : C:&#92;src&#92;blog&#92;content&#92;blog&#92;2017&#92;04&#92;checking-sql-agent-job-ownership-with-pester&#92;index.md&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# SpellingError : msdb&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# RunspaceId    : 8bc74548-2f8c-4f2b-b350-5f2d5c2dc033&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# ...snip...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All that&#39;s left now is to group these by &lt;code&gt;SpellingError&lt;/code&gt;, and then include a count of files with that error as well as a raw count. The grouping and counting is straightforward - where it gets interesting is the unique count.&lt;/p&gt;
&lt;p&gt;We&#39;re adding a custom property on &lt;code&gt;Select-Object&lt;/code&gt; by creating a hashtable (which takes a label/expression pair). The group (from &lt;code&gt;Group-Object&lt;/code&gt;) contains an array of all the objects in that group, so we can count the unique values of &lt;code&gt;FileName&lt;/code&gt; with &lt;code&gt;Get-Unique&lt;/code&gt; and &lt;code&gt;Measure-Object&lt;/code&gt;. It&#39;s a rather busy one-liner.&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$out&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;ConvertFrom-String&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;TemplateContent &lt;span class=&quot;token variable&quot;&gt;$template&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;Select-Object&lt;/span&gt; SpellingError&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; FileName &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;Group-Object&lt;/span&gt; SpellingError &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;Select-Object&lt;/span&gt; Name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; @&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;l=&lt;span class=&quot;token string&quot;&gt;&#39;# Files&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;e=&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$_&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Group&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;FileName &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Get-Unique&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Measure-Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Count&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Count &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;Sort-Object&lt;/span&gt; Count &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Descending&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;the-full-script&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/04/getting-a-summary-of-cspell-errors-with-powershell/#the-full-script&quot;&gt;The full script&lt;/a&gt;&lt;/h2&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$out&lt;/span&gt; = npx cspell content/&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;md*&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$template&lt;/span&gt; = @&lt;span class=&quot;token string&quot;&gt;&#39;&lt;br /&gt;{FileName*:C:&#92;src&#92;blog&#92;content&#92;blog&#92;2020&#92;12&#92;analyze-your-create-react-app-bundle-size-without-ejecting&#92;index.md}:82:70 - Unknown word ({SpellingError:gatsbyjs})&lt;br /&gt;{FileName*:C:&#92;src&#92;blog&#92;content&#92;blog&#92;2017&#92;04&#92;checking-sql-agent-job-ownership-with-pester&#92;index.md}:17:40 - Unknown word ({SpellingError:msdb})&lt;br /&gt;&#39;&lt;/span&gt;@&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$out&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;ConvertFrom-String&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;TemplateContent &lt;span class=&quot;token variable&quot;&gt;$template&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;Select-Object&lt;/span&gt; SpellingError&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; FileName &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;Group-Object&lt;/span&gt; SpellingError &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;Select-Object&lt;/span&gt; Name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; @&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;l=&lt;span class=&quot;token string&quot;&gt;&#39;# Files&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;e=&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$_&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Group&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;FileName &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Get-Unique&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Measure-Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Count&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Count &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;Sort-Object&lt;/span&gt; Count &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Descending&lt;/code&gt;&lt;/pre&gt;
</description>
      <pubDate>Fri, 30 Apr 2021 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2021/04/getting-a-summary-of-cspell-errors-with-powershell/</guid>
    </item>
    <item>
      <title>Updating babel-eslint to @babel/eslint-parser for React apps</title>
      <link>https://tjaddison.com/blog/2021/03/updating-babel-eslint-to-babeleslint-parser-for-react-apps/</link>
      <description>&lt;p&gt;As of March 2020, &lt;code&gt;babel-eslint&lt;/code&gt; has been deprecated, and is now &lt;code&gt;@babel/eslint-parser&lt;/code&gt;. That doesn&#39;t stop it (as of March 2021) being downloaded &lt;em&gt;6.5 million times per week&lt;/em&gt;. You wouldn&#39;t know this unless you attempted to add it as a new dependency, in which case &lt;code&gt;npm&lt;/code&gt; would tell you:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm WARN deprecated babel-eslint@10.1.0: babel-eslint is now @babel/eslint-parser. This package will no longer receive updates.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Upgrading is straightforward but I couldn&#39;t find any clear guides - so if you want to avoid the trial and error you can follow the below steps (which I&#39;ve tested on &lt;a href=&quot;https://create-react-app.dev/&quot;&gt;Create React App&lt;/a&gt;, &lt;a href=&quot;https://nextjs.org/&quot;&gt;Next.js&lt;/a&gt;, and &lt;a href=&quot;https://vitejs.dev/&quot;&gt;Vite&lt;/a&gt; apps - which all use Babel under the hood).&lt;/p&gt;
&lt;p&gt;As to &lt;em&gt;why&lt;/em&gt; &lt;code&gt;babel-eslint&lt;/code&gt; has been deprecated? It&#39;s documented in the (now archived) &lt;a href=&quot;https://github.com/babel/babel-eslint&quot;&gt;babel-eslint repo&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;As of the v11.x.x release, babel-eslint now requires Babel as a peer dependency and expects a valid &lt;a href=&quot;https://babeljs.io/docs/en/configuration&quot;&gt;Babel configuration file&lt;/a&gt; to exist. This ensures that the same Babel configuration is used during both linting and compilation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;short-version&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/03/updating-babel-eslint-to-babeleslint-parser-for-react-apps/#short-version&quot;&gt;Short version&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Remove &lt;code&gt;babel-eslint&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;@babel/eslint-parser&lt;/code&gt; &lt;code&gt;@babel/preset-react&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;You may also need the peer dependency &lt;code&gt;@babel/core&lt;/code&gt; (npm7 installs peer dependencies by default)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Update the parser in your &lt;code&gt;.eslintrc.*&lt;/code&gt; file (from &lt;code&gt;babel-eslint&lt;/code&gt; to &lt;code&gt;@babel/eslint-parser&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Add the following to the &lt;code&gt;parserOptions&lt;/code&gt; configuration in your &lt;code&gt;.eslintrc.*&lt;/code&gt; file:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token literal-property property&quot;&gt;requireConfigFile&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token literal-property property&quot;&gt;babelOptions&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;presets&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;@babel/preset-react&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;longer-version&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/03/updating-babel-eslint-to-babeleslint-parser-for-react-apps/#longer-version&quot;&gt;Longer version&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First, switch out the Babel eslint plugin by removing &lt;code&gt;babel-eslint&lt;/code&gt; and installing &lt;code&gt;@babel/eslint-parser&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; uninstall babel-eslint&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; @babel/eslint-parser &lt;span class=&quot;token parameter variable&quot;&gt;-D&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;If you&#39;re not running npm 7 (Node15) you will also need to add &lt;code&gt;@babel/core&lt;/code&gt;, which is a &lt;a href=&quot;https://nodejs.org/en/blog/npm/peer-dependencies/&quot;&gt;peer dependency&lt;/a&gt; of &lt;code&gt;@babel/eslint-parser&lt;/code&gt;. From npm 7 peer dependencies are installed by default.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And then update the parser (in your &lt;code&gt;.eslintrc.*&lt;/code&gt; file):&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; parser: &#39;babel-eslint&#39;,&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; parser: &#39;@babel/eslint-parser&#39;,&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you run eslint now you&#39;ll now get an error about config files:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; 0:0  error  Parsing error: No Babel config file detected for C:&#92;temp&#92;site-test&#92;tailwind.config.js. Either disable config file checking with requireConfigFile: false, or configure Babel so that it can find the config files
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To fix this we need to modify &lt;code&gt;parserOptions&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;  parserOptions: {&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;    requireConfigFile: false,&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;  },&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And now if we run eslint again? An error parsing React code (specifically jsx), which helpfully tells us how to fix the issue:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; 18:4  error  Parsing error: C:&#92;temp&#92;site-test&#92;src&#92;templates&#92;blog-post.js: Support for the experimental syntax &#39;jsx&#39; isn&#39;t currently enabled (18:5):
  16 |
  17 |   return (
&amp;gt; 18 |     &amp;lt;Layout&amp;gt;
     |     ^

Add @babel/preset-react (https://git.io/JfeDR) to the &#39;presets&#39; section of your Babel config to enable transformation.
If you want to leave it as-is, add @babel/plugin-syntax-jsx (https://git.io/vb4yA) to the &#39;plugins&#39; section to enable parsing
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So let&#39;s install the plugin:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; @babel/preset-react &lt;span class=&quot;token parameter variable&quot;&gt;-D&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then update our parserOptions to pass this option through to Babel:&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;parserOptions: {&lt;br /&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; requireConfigFile: false,&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;  babelOptions: {&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;    presets: [&quot;@babel/preset-react&quot;],&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;  },&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;},&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we&#39;ll finally be able to run eslint!&lt;/p&gt;
</description>
      <pubDate>Wed, 31 Mar 2021 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2021/03/updating-babel-eslint-to-babeleslint-parser-for-react-apps/</guid>
    </item>
    <item>
      <title>Spell checking your Markdown blog posts with cspell</title>
      <link>https://tjaddison.com/blog/2021/02/spell-checking-your-markdown-blog-posts-with-cspell/</link>
      <description>&lt;p&gt;I love authoring in Markdown as there is almost no friction - just write. When the time comes to publish though - at minimum you&#39;ll want a preview, and a spell check. If you&#39;re using VS Code then the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker&quot;&gt;code spell checker extension&lt;/a&gt; is the fastest way to get started (don&#39;t be fooled by the fact it says &#39;code spell checker&#39; - it&#39;ll happily check any file, including Markdown).&lt;/p&gt;
&lt;p&gt;But what if you&#39;ve got a lot of posts you want to check? And what if perhaps you haven&#39;t been all that fastidious about spell-checking your posts in the past? That&#39;s where I was a few weeks ago, and I was happy to find there&#39;s a simple solution that requires nothing more than Node 12...&lt;/p&gt;
&lt;h2 id=&quot;checking-all-files-in-a-folder&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/02/spell-checking-your-markdown-blog-posts-with-cspell/#checking-all-files-in-a-folder&quot;&gt;Checking all files in a folder&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To check all files anywhere under the &lt;code&gt;content/posts&lt;/code&gt; folder that are either Markdown (&lt;code&gt;.md&lt;/code&gt;) or MDX (&lt;code&gt;.mdx&lt;/code&gt;) we can use &lt;a href=&quot;https://github.com/streetsidesoftware/cspell/tree/master/packages/cspell&quot;&gt;cspell&lt;/a&gt; by running:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;npx cspell content/posts/**/*.md*&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&#39;ll then see every file and any unknown words:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2021/02/spell-checking-your-markdown-blog-posts-with-cspell/XiSR0maZqW-295.avif 295w, https://tjaddison.com/blog/2021/02/spell-checking-your-markdown-blog-posts-with-cspell/XiSR0maZqW-590.avif 590w, https://tjaddison.com/blog/2021/02/spell-checking-your-markdown-blog-posts-with-cspell/XiSR0maZqW-885.avif 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2021/02/spell-checking-your-markdown-blog-posts-with-cspell/XiSR0maZqW-295.webp 295w, https://tjaddison.com/blog/2021/02/spell-checking-your-markdown-blog-posts-with-cspell/XiSR0maZqW-590.webp 590w, https://tjaddison.com/blog/2021/02/spell-checking-your-markdown-blog-posts-with-cspell/XiSR0maZqW-885.webp 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2021/02/spell-checking-your-markdown-blog-posts-with-cspell/XiSR0maZqW-295.jpeg 295w, https://tjaddison.com/blog/2021/02/spell-checking-your-markdown-blog-posts-with-cspell/XiSR0maZqW-590.jpeg 590w, https://tjaddison.com/blog/2021/02/spell-checking-your-markdown-blog-posts-with-cspell/XiSR0maZqW-885.jpeg 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;cspell command line output&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2021/02/spell-checking-your-markdown-blog-posts-with-cspell/XiSR0maZqW-295.jpeg&quot; width=&quot;885&quot; height=&quot;290&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Running this in the VS Code terminal allows you to CTRL-click any of the errors and be taken straight to that word in the file.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is the same library that powers the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker&quot;&gt;code spell checker extension&lt;/a&gt; (that&#39;s what the c stands for in cspell), which means you&#39;ll see the same errors reported on the command line and when viewing the file.&lt;/p&gt;
&lt;p&gt;This is a good start, but what if we&#39;re not happy with the defaults cspell uses?&lt;/p&gt;
&lt;h2 id=&quot;changing-the-cspell-defaults&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/02/spell-checking-your-markdown-blog-posts-with-cspell/#changing-the-cspell-defaults&quot;&gt;Changing the cspell defaults&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you&#39;re using the VS Code extension then by default any changes you make to the settings will be reflected in the &lt;code&gt;.vscode/settings.json&lt;/code&gt; file - which probably isn&#39;t what you want if you plan on doing anything with cspell from the command line.&lt;/p&gt;
&lt;p&gt;I suggest you create a &lt;code&gt;cspell.json&lt;/code&gt; file in the root of your folder:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0.1&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And now any changes you make in the context of the workspace will be placed in this file, which is also used by default when running cspell from the command line. As an example, if I wanted to ensure a particular repo was always checked using British English (rather than the American English default) I could add the following to the &lt;code&gt;cspell.json&lt;/code&gt; file:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0.1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;language&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;en-GB&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The options available in the file are documented on the &lt;a href=&quot;https://github.com/streetsidesoftware/cspell/tree/master/packages/cspell&quot;&gt;cspell GitHub page&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;handling-unknown-words&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/02/spell-checking-your-markdown-blog-posts-with-cspell/#handling-unknown-words&quot;&gt;Handling unknown words&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After you&#39;ve worked through actual spelling mistakes, you&#39;ll likely be left with a lot of unknown words that need to be added to a dictionary or ignored. There are multiple ways to handle this (cspell allows for adding or ignoring words at the user, workspace, file, or block/line level). The workflow I&#39;ve settled on is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Words that are probably correct in any context I&#39;ll add to the user dictionary (these end up in the VS Code user &lt;code&gt;settings.json&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Words which are specific to a repo (abbreviations, technical terms) I&#39;ll add to the workspace dictionary (these end up in &lt;code&gt;cspell.json&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Words which are only correct in a specific file I&#39;ll add to &lt;em&gt;that file&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Words which are not correct but should be ignored I&#39;ll ignore for &lt;em&gt;that file&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My blog posts all contain yaml frontmatter which gives me a great place to place words I want to add/ignore for that file only - this is done with comments and some cspell keywords.&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; My first blog post&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# cSpell:words tjaddison Addison&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# cSpell:ignore COOOOOOL&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This isn&#39;t a great example as I&#39;d probably want my blog name (tjaddison) and my surname (Addison) to be added to the workspace dictionary. Because they&#39;re added as words they are eligible to appear as suggestions. If I&#39;d used the word &#39;COOOOOOL&#39; anywhere in the post I&#39;d probably not want to repeat myself (and really I should be asking myself why I&#39;d used it in the first place).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When working on any repos with other collaborators I generally try to avoid adding words to the user dictionary - otherwise those words will show up as unknown for anyone else running a spell check on that repo.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;installing-cspell-as-a-dev-dependency&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/02/spell-checking-your-markdown-blog-posts-with-cspell/#installing-cspell-as-a-dev-dependency&quot;&gt;Installing cspell as a dev dependency&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you want to shave a few seconds off your &lt;code&gt;npx cspell&lt;/code&gt; command and your repo happens to be a Node.js project then you can add &lt;code&gt;cspell&lt;/code&gt; as a dev dependency:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; cspell &lt;span class=&quot;token parameter variable&quot;&gt;--D&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This allows you to add it as a script in your &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token property&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;spell&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;cspell content/blog/**/*.md&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;next-steps&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/02/spell-checking-your-markdown-blog-posts-with-cspell/#next-steps&quot;&gt;Next steps&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&#39;ve only scratched the surface on what is possible with the &lt;a href=&quot;https://github.com/streetsidesoftware/cspell/tree/master/packages/cspell&quot;&gt;cspell package&lt;/a&gt; and the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker&quot;&gt;VS Code extension&lt;/a&gt; - the documentation for the package is worth reading through to get an idea of what can be accomplished. There are a handful of &lt;a href=&quot;https://github.com/streetsidesoftware/cspell-dicts&quot;&gt;custom dictionaries&lt;/a&gt; available, and once you&#39;ve got your unknown count down to zero it might be worth &lt;a href=&quot;https://seankilleen.com/2021/01/adding-spell-checking-to-my-blogs-build-process-with-github-actions-and-cspell/&quot;&gt;adding spell checking to your build&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&#39;m still working on getting my unknown count down to zero, and along the way I&#39;ve fixed some really terrible typos. Sorry if you&#39;ve had the misfortune of encountering any of those - at least going forwards I&#39;ve got a handle on them.&lt;/p&gt;
</description>
      <pubDate>Sun, 28 Feb 2021 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2021/02/spell-checking-your-markdown-blog-posts-with-cspell/</guid>
    </item>
    <item>
      <title>Migrating from the official Firestore SDK to Firebase-Firestore-Lite</title>
      <link>https://tjaddison.com/blog/2021/01/migrating-from-the-official-firestore-sdk-to-firebase-firestore-lite/</link>
      <description>&lt;p&gt;The official Firestore SDK for JavaScript is &lt;a href=&quot;https://bundlephobia.com/result?p=@firebase/firestore@2.1.7&quot;&gt;pretty big&lt;/a&gt; - and if you want to use auth too then you&#39;re easily looking at 130KB of gzip compressed code. The size of the bundles is a &lt;a href=&quot;https://github.com/firebase/firebase-js-sdk/issues/332&quot;&gt;known issue&lt;/a&gt;, and there is even a &lt;a href=&quot;https://github.com/firebase/firebase-js-sdk/issues/4368&quot;&gt;firebase alpha SDK&lt;/a&gt; on the horizon that is set to cut the size by up to 80%.&lt;/p&gt;
&lt;p&gt;But if you don&#39;t want to wait for the alpha to turn into a production release there is an unofficial alternative in the &lt;a href=&quot;https://github.com/samuelgozi/firebase-firestore-lite&quot;&gt;firebase-firestore-lite package&lt;/a&gt;. I&#39;ve migrated apps and seen 90% size reduction with no loss in functionality (although I&#39;m using basic CRUD only - no realtime or offline support). If you&#39;d like more detail about the performance benefits of migrating see the &lt;a href=&quot;https://github.com/samuelgozi/firebase-firestore-lite/wiki/Firebase-Alternative-SDK-Benchmarks&quot;&gt;benchmarks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The rest of this post will cover what migration looks for Firestore with Google auth.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is not a drop-in replacement - the alternative SDK has a different (simpler!) API and doesn&#39;t have feature parity with the official SDK. For details on what features are missing see &lt;a href=&quot;https://github.com/samuelgozi/firebase-firestore-lite#what-am-i-giving-up-by-using-this&quot;&gt;what am i giving up by using this&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;updating-packages&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/01/migrating-from-the-official-firestore-sdk-to-firebase-firestore-lite/#updating-packages&quot;&gt;Updating Packages&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In order to use Firestore with Google auth we need to use both the &lt;code&gt;firebase-auth-lite&lt;/code&gt; and &lt;code&gt;firebase-firestore-lite&lt;/code&gt; packages. We can also remove the &lt;code&gt;firebase&lt;/code&gt; package:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yarn remove firebase

yarn add firebase-auth-lite firebase-firestore-lite
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;configuring-auth&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/01/migrating-from-the-official-firestore-sdk-to-firebase-firestore-lite/#configuring-auth&quot;&gt;Configuring Auth&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Rather than needing an instance of &lt;code&gt;firebase&lt;/code&gt; (probably instantiated with &lt;code&gt;firebase.initializeApp({...})&lt;/code&gt; you pass your API key directly to &lt;code&gt;auth&lt;/code&gt;. You can delete any code using &lt;code&gt;firebase/app&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The below example is all you need to configure Google sign-in, assuming your project had already taken the steps necessary &lt;a href=&quot;https://firebase.google.com/docs/auth/web/google-signin&quot;&gt;to enable it&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The below code uses React. If you&#39;re using something else the main changes will be in how you handle the callback &lt;code&gt;auth.listen&lt;/code&gt; to store the signed-in user (or &lt;code&gt;null&lt;/code&gt; if the user signed out).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// auth.js&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Auth &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;firebase-auth-lite&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; auth &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Auth&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;apiKey&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;YOUR_API_KEY&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;redirectUri&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;origin&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// I spent a while wondering why nothing was working - you need the below to wire up handling of the redirect after signing in&lt;/span&gt;&lt;br /&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handleSignInRedirect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setUser&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// This is the callback where you need to store the user details&lt;/span&gt;&lt;br /&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;setUser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;signIn&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;signInWithProvider&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;google.com&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; signIn &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;migrating-code&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/01/migrating-from-the-official-firestore-sdk-to-firebase-firestore-lite/#migrating-code&quot;&gt;Migrating code&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Creating the database instance is a little different:&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; import &quot;firebase/firestore&quot;;&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; import firebase from &quot;../Firebase/firebase&quot;;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; import Auth from &quot;firebase-auth-lite&quot;;&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; import Database from &quot;firebase-firestore-lite&quot;;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; const db = firebase.firestore();&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; const auth = new Auth({&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;   apiKey: &quot;YOUR_API_KEY&quot;,&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; })&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; const db = new Database({ projectId: &quot;YOUR_PROJECT_ID&quot;, auth })&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We no longer have an instantiated &lt;code&gt;firebase&lt;/code&gt; instance from &lt;code&gt;firebase/app&lt;/code&gt;, so instead we pass through an &lt;code&gt;auth&lt;/code&gt; instance with our &lt;code&gt;apiKey&lt;/code&gt;. This doesn&#39;t need to be the same instance of &lt;code&gt;auth&lt;/code&gt; we configured sign-in with. Because we no longer configure &lt;code&gt;firebase&lt;/code&gt; (which would have contained our project id) we also need to pass &lt;code&gt;projectId&lt;/code&gt; to the &lt;code&gt;Database&lt;/code&gt; constructor.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It is possible to re-use the same &lt;code&gt;auth&lt;/code&gt; instance from the &lt;code&gt;auth.js&lt;/code&gt; example above, though I prefer to not export and expose that to the rest of my app.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;get-a-single-document&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/01/migrating-from-the-official-firestore-sdk-to-firebase-firestore-lite/#get-a-single-document&quot;&gt;Get a single document&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A few notable changes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Collections are referenced by &lt;code&gt;ref&lt;/code&gt; rather than &lt;code&gt;collection&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ref&lt;/code&gt; actually allows you to refer to documents &lt;em&gt;or&lt;/em&gt; collections&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Documents in a collection are referenced by &lt;code&gt;child&lt;/code&gt; rather than &lt;code&gt;doc&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;You can also use a path string (e.g. &lt;code&gt;/collection/documentId&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Documents are returned with their data by default (no need to call &lt;code&gt;data()&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; const itemRef = await db.collection(&quot;items&quot;).doc(id).get();&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; const item = itemRef.data();&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; const item = await db.ref(&quot;items&quot;).child(id).get();&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;// or&lt;br /&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; const item = await db.ref(`items/${id}`).get();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;get-all-documents-in-a-collection&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/01/migrating-from-the-official-firestore-sdk-to-firebase-firestore-lite/#get-all-documents-in-a-collection&quot;&gt;Get all documents in a collection&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;list&lt;/code&gt; method is used instead of calling &lt;code&gt;get&lt;/code&gt; on a collection. By default the &lt;code&gt;list&lt;/code&gt; method only returns a single page of 20 items - so if you want an entire collection you need to pass a large &lt;code&gt;pageSize&lt;/code&gt; to the list call.&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; const accountsResult = await db.collection(&quot;accounts&quot;).get();&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; const allAccounts = accountsResult.docs.map((d) =&gt; d.data());&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; const accountsResult = await db.ref(&quot;accounts&quot;).list({pageSize: 1000});&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; const allAccounts = accountsResult.documents;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;add-update-or-delete-a-document&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/01/migrating-from-the-official-firestore-sdk-to-firebase-firestore-lite/#add-update-or-delete-a-document&quot;&gt;Add, update or delete a document&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;No changes needed here - the mutate methods all work as they did before (albeit with different patterns to access a collection/individual document).&lt;/p&gt;
&lt;p&gt;If using a &lt;code&gt;serverTimestamp&lt;/code&gt; (&lt;a href=&quot;https://firebase.google.com/docs/reference/js/firebase.firestore.FieldValue#servertimestamp&quot;&gt;docs&lt;/a&gt;) you&#39;ll need to import &lt;code&gt;Transform&lt;/code&gt; (&lt;a href=&quot;https://samuelgozi.github.io/firebase-firestore-lite/classes/transform.html&quot;&gt;docs&lt;/a&gt;) and use that, rather than the &lt;code&gt;FieldValue.serverTimestamp()&lt;/code&gt; from the Firestore SDK:&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; import Transform from &quot;firebase-firestore-lite/dist/Transform&quot;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; const serverTimestamp = firebase.firestore.FieldValue.serverTimestamp();&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;await itemRef.update({&lt;br /&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;  updatedAt: serverTimestamp,&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;  updatedAt: new Transform(&quot;serverTimestamp&quot;),&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;})&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;query-documents&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/01/migrating-from-the-official-firestore-sdk-to-firebase-firestore-lite/#query-documents&quot;&gt;Query documents&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A few differences here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rather than chaining calls to &lt;code&gt;where&lt;/code&gt; we pass an array of filters (each of which is an array)&lt;/li&gt;
&lt;li&gt;We call &lt;code&gt;run&lt;/code&gt; on the query instead of &lt;code&gt;get&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;If we want the document&#39;s id we need to query the &lt;code&gt;__meta__&lt;/code&gt; property&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;  const itemsResult = await db.collection(&quot;items&quot;)&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;    .where(&quot;reportingDate&quot;, &quot;&gt;=&quot;, fromDate)&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;    .where(&quot;accountId&quot;, &quot;==&quot;, accountId)&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;    .get();&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;  const items = itemsResult.docs.map((d) =&gt; {&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;    return { ...d.data(), id: d.id };&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;  });&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;  const itemsQuery = db.ref(&quot;items&quot;)&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;    .query({&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;      where: [&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;        [&#39;reportingDate&#39;, &#39;&gt;=&#39;, fromDate],&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;        [&#39;accountId&#39;, &#39;==&#39;, accountId]&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;       ]&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;    })&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;  const itemsResult = await itemsQuery.run();&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;  const allItems = allItemsResult.map((d) =&gt; {&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;    return { ...d, id: d.__meta__.id };&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;  });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;summary&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2021/01/migrating-from-the-official-firestore-sdk-to-firebase-firestore-lite/#summary&quot;&gt;Summary&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you&#39;re looking for a reduced bundle size, easier API, are comfortable with an unofficial SDK, and don&#39;t need offline or realtime support - then &lt;code&gt;firebase-firestore-lite&lt;/code&gt; is worth checking out. I&#39;ve only scratched the surface of what is supported - read through the &lt;a href=&quot;https://github.com/samuelgozi/firebase-firestore-lite&quot;&gt;documentation for firebase-firestore-lite&lt;/a&gt; for a complete overview.&lt;/p&gt;
&lt;p&gt;The alpha SDK sounds promising, though who knows when a production-ready release is coming (weeks, months, years?). Until then (and maybe even after if the API is still so clunky) I&#39;ll keep reaching for &lt;code&gt;firebase-firestore-lite&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you&#39;re using Firestore for the first time I&#39;d still suggest you use the official SDK. When you need to search for problems (and you undoubtedly will be - the docs and some of the design choices/behaviors are...interesting - see &lt;a href=&quot;https://acko.net/blog/the-database-is-on-fire/&quot;&gt;the database is on fire&lt;/a&gt;) you&#39;ll need to be searching for the right terms/methods/etc.&lt;/p&gt;
</description>
      <pubDate>Sun, 31 Jan 2021 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2021/01/migrating-from-the-official-firestore-sdk-to-firebase-firestore-lite/</guid>
    </item>
    <item>
      <title>Analyze your Create React App bundle size - without ejecting</title>
      <link>https://tjaddison.com/blog/2020/12/analyze-your-create-react-app-bundle-size-without-ejecting/</link>
      <description>&lt;p&gt;After looking at my [Gatsby site bundle size last month] I figured I should do the same for a few &lt;a href=&quot;https://create-react-app.dev/&quot;&gt;create-react-app (CRA)&lt;/a&gt; based sites. If you&#39;re wondering &lt;em&gt;why&lt;/em&gt; you might want to do that, check out the previous post, otherwise read on for the details.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The following steps have been tested with CRA 4&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;step-1-dependencies&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/12/analyze-your-create-react-app-bundle-size-without-ejecting/#step-1-dependencies&quot;&gt;Step 1 - Dependencies&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The plugin we&#39;ll use to analyze bundle size is &lt;a href=&quot;https://github.com/webpack-contrib/webpack-bundle-analyzer&quot;&gt;webpack-bundle-analyzer&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To avoid ejecting we&#39;ll be using &lt;a href=&quot;https://github.com/gsoft-inc/craco&quot;&gt;CRACO (Create React App Configuration Override)&lt;/a&gt; to let us add webpack plugins to the build.&lt;/p&gt;
&lt;p&gt;And finally we&#39;ll use &lt;a href=&quot;https://www.npmjs.com/package/cross-env&quot;&gt;cross-env&lt;/a&gt; to customize our build scripts in a way that will work on Windows too.&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;yarn&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; webpack-bundle-analyzer @craco/craco cross-env &lt;span class=&quot;token parameter variable&quot;&gt;-D&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;step-2-inject-bundle-analyzer-plugin&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/12/analyze-your-create-react-app-bundle-size-without-ejecting/#step-2-inject-bundle-analyzer-plugin&quot;&gt;Step 2 - Inject bundle analyzer plugin&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Create the &lt;code&gt;craco.config.js&lt;/code&gt; file in your projects root folder, and paste the below JavaScript in. When we wire CRACO up this will load the analyzer plugin for any production builds. Additionally, it&#39;ll default to dumping a json report file in the output folder (useful for CI builds), and only launch the interactive analyzer when the &lt;code&gt;REACT_APP_INTERACTIVE_ANALYZE&lt;/code&gt; environment variable is set.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// craco.config.js&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; webpack &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;webpack&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; BundleAnalyzerPlugin &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;webpack-bundle-analyzer&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;BundleAnalyzerPlugin&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; env &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; isProductionBuild &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;NODE_ENV&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;production&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; analyzerMode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;REACT_APP_INTERACTIVE_ANALYZE&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;server&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;json&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; plugins &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;isProductionBuild&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    plugins&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BundleAnalyzerPlugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; analyzerMode &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;webpack&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      plugins&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;step-3-wire-up-craco&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/12/analyze-your-create-react-app-bundle-size-without-ejecting/#step-3-wire-up-craco&quot;&gt;Step 3 - Wire up CRACO&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Modify the &lt;code&gt;package.json&lt;/code&gt; file to use CRACO:&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&quot;scripts&quot;: {&lt;br /&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;   &quot;start&quot;: &quot;react-scripts start&quot;,&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;   &quot;start&quot;: &quot;craco start&quot;,&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;   &quot;build&quot;: &quot;react-scripts build&quot;,&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;   &quot;build&quot;: &quot;craco build&quot;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;   &quot;test&quot;: &quot;react-scripts test&quot;,&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;   &quot;test&quot;: &quot;craco test&quot;&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;   &quot;analyze&quot;: &quot;cross-env REACT_APP_INTERACTIVE_ANALYZE=1 npm run build&quot;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The new &lt;code&gt;analyze&lt;/code&gt; command will set the environment variable required to launch the interactive analyzer. We have to prefix our environment variable with &lt;code&gt;REACT_APP_&lt;/code&gt; to &lt;a href=&quot;https://create-react-app.dev/docs/adding-custom-environment-variables/&quot;&gt;have it injected by CRA&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;step-4-take-a-look-at-your-bundles&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/12/analyze-your-create-react-app-bundle-size-without-ejecting/#step-4-take-a-look-at-your-bundles&quot;&gt;Step 4 - Take a look at your bundles&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Your production builds will now have a &lt;code&gt;report.json&lt;/code&gt; in the root folder that you can consume from CI builds or look at in production. If you want to use the interactive analyzer you can run the analyze command:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;yarn&lt;/span&gt; analyze&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2020/12/analyze-your-create-react-app-bundle-size-without-ejecting/a-xWIiaqXa-295.avif 295w, https://tjaddison.com/blog/2020/12/analyze-your-create-react-app-bundle-size-without-ejecting/a-xWIiaqXa-590.avif 590w, https://tjaddison.com/blog/2020/12/analyze-your-create-react-app-bundle-size-without-ejecting/a-xWIiaqXa-885.avif 885w, https://tjaddison.com/blog/2020/12/analyze-your-create-react-app-bundle-size-without-ejecting/a-xWIiaqXa-1180.avif 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2020/12/analyze-your-create-react-app-bundle-size-without-ejecting/a-xWIiaqXa-295.webp 295w, https://tjaddison.com/blog/2020/12/analyze-your-create-react-app-bundle-size-without-ejecting/a-xWIiaqXa-590.webp 590w, https://tjaddison.com/blog/2020/12/analyze-your-create-react-app-bundle-size-without-ejecting/a-xWIiaqXa-885.webp 885w, https://tjaddison.com/blog/2020/12/analyze-your-create-react-app-bundle-size-without-ejecting/a-xWIiaqXa-1180.webp 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2020/12/analyze-your-create-react-app-bundle-size-without-ejecting/a-xWIiaqXa-295.jpeg 295w, https://tjaddison.com/blog/2020/12/analyze-your-create-react-app-bundle-size-without-ejecting/a-xWIiaqXa-590.jpeg 590w, https://tjaddison.com/blog/2020/12/analyze-your-create-react-app-bundle-size-without-ejecting/a-xWIiaqXa-885.jpeg 885w, https://tjaddison.com/blog/2020/12/analyze-your-create-react-app-bundle-size-without-ejecting/a-xWIiaqXa-1180.jpeg 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Create React App Bundle&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2020/12/analyze-your-create-react-app-bundle-size-without-ejecting/a-xWIiaqXa-295.jpeg&quot; width=&quot;1180&quot; height=&quot;732&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
</description>
      <pubDate>Thu, 31 Dec 2020 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2020/12/analyze-your-create-react-app-bundle-size-without-ejecting/</guid>
    </item>
    <item>
      <title>Monitoring your GatsbyJS bundle size</title>
      <link>https://tjaddison.com/blog/2020/11/monitoring-your-gatsbyjs-bundle-size/</link>
      <description>&lt;p&gt;Gatsby will do a great job of serving up a well optimized site - but it is constrained by what &lt;em&gt;you told it&lt;/em&gt; to serve. If keeping your site lean is important to you, then looking at your bundle size (and what&#39;s in that bundle) before and after adding any new dependencies can be illuminating. If you&#39;re currently happy with performance (hundreds dof kilobytes of JavaScript can be added and you&#39;ll still score a perfect 100 on a &lt;a href=&quot;https://web.dev/measure/&quot;&gt;web.dev test&lt;/a&gt;) then I highly recommend &lt;a href=&quot;https://danluu.com/&quot;&gt;Dan Luu&lt;/a&gt;&#39;s &lt;a href=&quot;https://danluu.com/web-bloat/&quot;&gt;web bloat&lt;/a&gt;. After you&#39;ve read that and now feel duly chastened about the state of the modern web, let&#39;s look at two ways to analyze your Gatsby site&#39;s bundle - interactive, and build-time reports.&lt;/p&gt;
&lt;h2 id=&quot;configuring-the-webpack-size-plugin&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/11/monitoring-your-gatsbyjs-bundle-size/#configuring-the-webpack-size-plugin&quot;&gt;Configuring the webpack size plugin&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are a couple of Gatsby plugins which will add &lt;a href=&quot;https://github.com/webpack-contrib/webpack-bundle-analyzer&quot;&gt;webpack-bundle-analyzer&lt;/a&gt; for you, though I&#39;ve opted to consume that package directly.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Gatsby plugins that are wrappers for other packages can be really hit or miss. Will they expose all the features you want, and be updated in a timely manner? In many cases I&#39;ve opted to consume the packages directly and skip the Gatsby plugin as the amount of code needed is small and I appreciate the opportunity to learn more about the bundler and the analyzer plugin. If you prefer to stick with Gatsby plugins then I&#39;d suggest &lt;a href=&quot;https://github.com/JimmyBeldone/gatsby-plugin-webpack-bundle-analyser-v2&quot;&gt;gatsby-plugin-webpack-bundle-analyser-v2&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;First of all you&#39;ll need to install the plugin (I&#39;m using &lt;code&gt;yarn&lt;/code&gt; in my examples):&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;yarn&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; webpack-bundle-analyzer &lt;span class=&quot;token parameter variable&quot;&gt;-D&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next you need to add the following to &lt;code&gt;gatsby-node.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; BundleAnalyzerPlugin &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;webpack-bundle-analyzer&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;BundleAnalyzerPlugin&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;exports&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;onCreateWebpackConfig&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; stage&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; actions &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; analyzerMode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;INTERACTIVE_ANALYZE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;server&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;json&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;stage &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;build-javascript&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    actions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setWebpackConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BundleAnalyzerPlugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          analyzerMode&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token literal-property property&quot;&gt;reportFileName&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;./__build/bundlereport.json&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.gatsbyjs.com/docs/how-to/custom-configuration/add-custom-webpack-config/&quot;&gt;docs for onCreateWebpackConfig&lt;/a&gt; explain the different stages, and we only want to run the analyzer plugin when we&#39;re building our production JavaScript bundles (&lt;code&gt;build-javascript&lt;/code&gt;) - this stage only runs during a &lt;code&gt;gatsby build&lt;/code&gt;, and not during development.&lt;/p&gt;
&lt;p&gt;The arguments we pass through are explained in the below sections and determine if we want to look at our bundle size interactively, or in a JSON report.&lt;/p&gt;
&lt;h2 id=&quot;viewing-bundle-sizes-interactively&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/11/monitoring-your-gatsbyjs-bundle-size/#viewing-bundle-sizes-interactively&quot;&gt;Viewing bundle sizes interactively&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To explore the current state of your bundle I&#39;d suggest running the analyzer in &lt;em&gt;interactive&lt;/em&gt; mode. This will launch a server (defaulting to &lt;code&gt;localhost:8888&lt;/code&gt;) that hosts an interactive treemap of your bundles:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2020/11/monitoring-your-gatsbyjs-bundle-size/QMK-AbLgcF-295.avif 295w, https://tjaddison.com/blog/2020/11/monitoring-your-gatsbyjs-bundle-size/QMK-AbLgcF-590.avif 590w, https://tjaddison.com/blog/2020/11/monitoring-your-gatsbyjs-bundle-size/QMK-AbLgcF-885.avif 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2020/11/monitoring-your-gatsbyjs-bundle-size/QMK-AbLgcF-295.webp 295w, https://tjaddison.com/blog/2020/11/monitoring-your-gatsbyjs-bundle-size/QMK-AbLgcF-590.webp 590w, https://tjaddison.com/blog/2020/11/monitoring-your-gatsbyjs-bundle-size/QMK-AbLgcF-885.webp 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2020/11/monitoring-your-gatsbyjs-bundle-size/QMK-AbLgcF-295.jpeg 295w, https://tjaddison.com/blog/2020/11/monitoring-your-gatsbyjs-bundle-size/QMK-AbLgcF-590.jpeg 590w, https://tjaddison.com/blog/2020/11/monitoring-your-gatsbyjs-bundle-size/QMK-AbLgcF-885.jpeg 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;A recent snapshot of this websites bundles&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2020/11/monitoring-your-gatsbyjs-bundle-size/QMK-AbLgcF-295.jpeg&quot; width=&quot;885&quot; height=&quot;569&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;To launch the analyzer in interactive mode we need to configure an environment variable (&lt;code&gt;INTERACTIVE_ANALYZE&lt;/code&gt;). To support Windows we&#39;ll first add the &lt;a href=&quot;https://www.npmjs.com/package/cross-env&quot;&gt;cross-env&lt;/a&gt; package:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;yarn&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; cross-env &lt;span class=&quot;token parameter variable&quot;&gt;-D&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we&#39;ll then add a new script that will launch the build process with this environment variable.&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* package.json */&lt;/span&gt;&lt;br /&gt;scripts&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;analyze&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;cross-env INTERACTIVE_ANALYZE=1 npm run build&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once you run the &lt;code&gt;analyze&lt;/code&gt; script the analyzer page will launch in a browser when the build process completes.&lt;/p&gt;
&lt;p&gt;Something to keep in mind is that all of these bundles aren&#39;t loaded for every page/every browser. A good example is the &lt;code&gt;polyfills.js&lt;/code&gt; bundle which &lt;a href=&quot;https://github.com/gatsbyjs/gatsby/issues/28736&quot;&gt;won&#39;t be loaded on older browsers at all&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;reporting-on-bundle-sizes-at-build-time&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/11/monitoring-your-gatsbyjs-bundle-size/#reporting-on-bundle-sizes-at-build-time&quot;&gt;Reporting on bundle sizes at build time&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;By default every &lt;code&gt;gatsby build&lt;/code&gt; will log the bundle stats to the file &lt;code&gt;__build/bundlereport.json&lt;/code&gt;. You can see the &lt;a href=&quot;https://tjaddison.com/__build/bundlereport.json&quot;&gt;current bundle stats for tjaddison.com&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you don&#39;t want to upload your bundle report to your production site you can either gate the stats behind another environment variable (which you won&#39;t set for publishing).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Where this really shines is comparing releases - I use &lt;a href=&quot;https://www.netlify.com/&quot;&gt;Netlify&lt;/a&gt; to deploy my site and take advantage of &lt;a href=&quot;https://docs.netlify.com/site-deploys/overview/#deploy-preview-controls&quot;&gt;deploy previews&lt;/a&gt; to preview every pull request as a branch. This publishes my branch (including my &lt;code&gt;bundlereport.json&lt;/code&gt;), which allows me to take those files and compare the bundle sizes of the branch compared to production.&lt;/p&gt;
&lt;p&gt;As I make so few changes to the bundles right now I&#39;m eyeballing the numbers - by using the below code I can print out the stats from production and a branch (in this case I&#39;m comparing the before/after numbers from updating to tailwindcss 2.0 and bumping a few other dependencies):&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;logParsedReport&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;report&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; parsedTotal &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; gzipTotal &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; file &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; report&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; label&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; parsedSize&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; gzipSize &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    parsedTotal &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; parsedSize&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    gzipTotal &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; gzipSize&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Name: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;label&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; - &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;parsedSize&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; parsed (&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;gzipSize&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; gzip)&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;---------&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Total &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;parsedTotal&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; (&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;gzipTotal&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; gzip)&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; prod &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://tjaddison.com/__build/bundlereport.json&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; branch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&quot;https://5fc8401d8292a600084390dd--fervent-lovelace-f75e7a.netlify.app/__build/bundlereport.json&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prod&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;logParsedReport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;json&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;branch&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;logParsedReport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;json&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gives the following output (which I&#39;ve truncated for brevity):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Name: 942fc3de6d17fbede2785a28a3625a2fd54381eb-469888aa0543c06173b4.js - 18354 parsed (6526 gzip)
Name: app-5ff60cbc19fc47db0c13.js - 66067 parsed (18874 gzip)
Name: component---cache-caches-gatsby-plugin-offline-app-shell-js-16703ee5599528db9f93.js - 499 parsed (351 gzip)
...
---------
Total 341617 (111583 gzip)

Name: 942fc3de6d17fbede2785a28a3625a2fd54381eb-0efa3ed3379b611c2795.js - 18152 parsed (6444 gzip)
Name: app-fd4d3ac6b7f96cbbf5f8.js - 74754 parsed (22347 gzip)
Name: component---cache-caches-gatsby-plugin-offline-app-shell-js-16703ee5599528db9f93.js - 499 parsed (351 gzip)
...
---------
Total 349385 (114705 gzip)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Happily this change has reduced the total bundle size - though as the code isn&#39;t actually doing a diff I couldn&#39;t tell you where. If you come up with a better piece of diffing code for the output please let me know!&lt;/p&gt;
</description>
      <pubDate>Mon, 30 Nov 2020 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2020/11/monitoring-your-gatsbyjs-bundle-size/</guid>
    </item>
    <item>
      <title>Interesting Links - October 2020</title>
      <link>https://tjaddison.com/blog/2020/10/interesting-links-October-2020/</link>
      <description>&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://keavy.com/&quot;&gt;Keavy McMinn&lt;/a&gt; shares some great examples of &lt;a href=&quot;https://keavy.com/work/thriving-on-the-technical-leadership-path/&quot;&gt;what senior technical leadership can look like&lt;/a&gt; (all engineering career ladders should not lead to management!).&lt;/li&gt;
&lt;li&gt;In addition to being a pretty terrifying read from a security perspective, this fascinating journey through the process of &lt;a href=&quot;https://whynotsecurity.com/blog/teamviewer/&quot;&gt;discovering a vulnerability in TeamViewer&lt;/a&gt; offers a glimpse at a pretty exciting red-team engagement.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/refactoring-ui/7-practical-tips-for-cheating-at-design-40c736799886&quot;&gt;7 helpful design tips&lt;/a&gt; that should be useful even if you profess to have no design talent (&lt;em&gt;raises hand&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Ever wondered why Mozilla shows up in so many user agents (even when you&#39;re not using Firefox)? - the &lt;a href=&quot;https://webaim.org/blog/user-agent-string-history/&quot;&gt;history of the browser user-agent string&lt;/a&gt; will clue you in.&lt;/li&gt;
&lt;li&gt;If you enjoyed &lt;a href=&quot;https://medium.com/basecs&quot;&gt;basecs&lt;/a&gt;, you&#39;ll probably enjoy it&#39;s distributed systems cousin &lt;a href=&quot;https://medium.com/baseds&quot;&gt;baseds&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;An incredible &lt;a href=&quot;https://www.destroyallsoftware.com/compendium/network-protocols?share_key=97d3ba4c24d21147&quot;&gt;introduction to networking for programmers&lt;/a&gt; from routing down to the physical (analog!) layer.&lt;/li&gt;
&lt;li&gt;Unlike in RPGs, &lt;a href=&quot;https://noidea.dog/blog/surviving-the-organisational-side-quest&quot;&gt;side quests&lt;/a&gt; in reality are pretty key to advancing the &#39;main story&#39; (getting stuff done).&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/@rbranson&quot;&gt;Rick Branson&lt;/a&gt; argues that you &lt;a href=&quot;https://medium.com/@rbranson/why-you-shouldnt-count-production-incidents-38616d8e6329&quot;&gt;you shouldn&#39;t count production incidents&lt;/a&gt; if you want to improve quality.&lt;/li&gt;
&lt;li&gt;When someone tells you they&#39;re not biased, perhaps you could suggest this thorough article that reminds us &lt;a href=&quot;https://medium.com/tech-diversity-files/if-you-think-women-in-tech-is-just-a-pipeline-problem-you-haven-t-been-paying-attention-cb7a2073b996&quot;&gt;we still have a lot of work to do to remove gender bias in tech&lt;/a&gt;.
&lt;ul&gt;
&lt;li&gt;And once you get past that lack of bias, maybe have a discussion on meritocracy (hint: &lt;a href=&quot;https://medium.com/@cathy_67575/the-fallacy-of-meritocracy-d8260f5f0611&quot;&gt;it&#39;s a a fallacy&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://krebsonsecurity.com/2020/02/dangerous-domain-corp-com-goes-up-for-sale/&quot;&gt;Corp.com is up for sale&lt;/a&gt;, and that&#39;s a bit of a problem if your Active Directory domain is configured in a certain way (a way which corresponds to a set of defaults proposed by Microsoft in the past!).&lt;/li&gt;
&lt;li&gt;In addition to being an (extremely) fast JavaScript bundler, &lt;a href=&quot;https://esbuild.github.io/&quot;&gt;esbuild&lt;/a&gt; has a fantastic set of &lt;a href=&quot;https://github.com/evanw/esbuild/blob/master/docs/architecture.md&quot;&gt;architecture docs&lt;/a&gt; that (for a relatively short document) gave me a clear picture of how a bundler works.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://maggieappleton.com/&quot;&gt;Maggie Appleton&#39;s site&lt;/a&gt; is a growing collection of &lt;a href=&quot;https://maggieappleton.com/essays&quot;&gt;illustrated essays&lt;/a&gt; as well as a &lt;a href=&quot;https://maggieappleton.com/garden&quot;&gt;digital garden&lt;/a&gt; that serve as both great introduction to topics, works of art, and novel metaphors that may help concepts stick (from React hooks to databases).&lt;/li&gt;
&lt;li&gt;If you&#39;ve ever had mixed feelings about Object/Relational-Mapping then you&#39;ll enjoy the essay &lt;a href=&quot;http://blogs.tedneward.com/post/the-vietnam-of-computer-science/&quot;&gt;The Vietnam of computer science&lt;/a&gt;. Just don&#39;t expect a clear resolution at the end.&lt;/li&gt;
&lt;li&gt;Trying to understand &lt;a href=&quot;https://www.red-gate.com/simple-talk/sql/database-administration/how-does-accelerated-database-recovery-work/&quot;&gt;how accelerated database recovery works&lt;/a&gt; is far easier once you&#39;ve read this article from Forrest McDaniel. The animations bring the processes to life in a way no amount of testing (or re-reading whitepapers) could.&lt;/li&gt;
&lt;li&gt;And finally, three great papers covered by the &lt;a href=&quot;https://blog.acolyer.org/&quot;&gt;the morning paper&lt;/a&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.acolyer.org/2020/02/26/meaningful-availability/&quot;&gt;Meaningful Availability&lt;/a&gt; introduces &lt;em&gt;windowed user-uptime&lt;/em&gt; as a better way of measuring service availability.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.acolyer.org/2020/02/21/extending-relational-query-processing/&quot;&gt;Extending relational query processing with ML inference&lt;/a&gt; is all embedding ML in a database engine. Using SQL Server cores to run ML sounds pretty expensive, but if it can be fast enough to offset the need to ship the data around, maybe moving the model to the data is the way to go.&lt;/li&gt;
&lt;li&gt;When you might be rolling out 600 releases a day, how do you spot the bad ones? &lt;a href=&quot;https://blog.acolyer.org/2020/02/28/microsoft-gandalf/&quot;&gt;Gandalf&lt;/a&gt; is Azure&#39;s answer, and it does a pretty impressive job.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Sat, 31 Oct 2020 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2020/10/interesting-links-October-2020/</guid>
    </item>
    <item>
      <title>You don&#39;t have to use graphql to query your Gatsby site&#39;s metadata</title>
      <link>https://tjaddison.com/blog/2020/09/you-dont-have-to-use-graphql-to-query-your-gatsby-sites-metadata/</link>
      <description>&lt;h2 id=&quot;the-challenge&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/09/you-dont-have-to-use-graphql-to-query-your-gatsby-sites-metadata/#the-challenge&quot;&gt;The Challenge&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The learning curve for GatsbyJS can be pretty rough. For me, a big part of that was feeling that you &lt;em&gt;had&lt;/em&gt; to use GraphQL or you were being distinctly &#39;un-Gatsby-like&#39;. In addition to the documentation featuring it almost exclusively, every starter (from the official starters to popular community ones) had GraphQL everywhere. Because there were no counter-examples I happily went along and dutifully queried all my &lt;code&gt;siteMetadata&lt;/code&gt; via GraphQL, as &lt;a href=&quot;https://www.gatsbyjs.com/docs/gatsby-config/#sitemetadata&quot;&gt;the docs requested&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For an imaginary component that renders a link to my &lt;a href=&quot;https://twitter.com/tjaddison&quot;&gt;Twitter bio&lt;/a&gt; that would mean the following in &lt;code&gt;gatsby-config.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token literal-property property&quot;&gt;site&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;siteMetadata&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;social&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;twitter&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;tjaddison&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then this code to query that value:&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// TwitterLink.js&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; useStaticQuery&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; graphql &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gatsby&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;TwitterLink&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; site &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useStaticQuery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    graphql&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&lt;br /&gt;      query {&lt;br /&gt;        site {&lt;br /&gt;          siteMetadata {&lt;br /&gt;            social {&lt;br /&gt;              twitter&lt;br /&gt;            }&lt;br /&gt;          }&lt;br /&gt;        }&lt;br /&gt;      }&lt;br /&gt;    &lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;https://twitter.com/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;site&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;siteMetadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;social&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;twitter&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;      Twitter&lt;br /&gt;    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After teaching Gatsby to a few people (and having to apologize for the amount of boilerplate almost every time) and building out a dozen or so sites...I&#39;m pretty confident that there are &lt;em&gt;several&lt;/em&gt; better ways of handling this.&lt;/p&gt;
&lt;h3 id=&quot;solution-1-hardcode&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/09/you-dont-have-to-use-graphql-to-query-your-gatsby-sites-metadata/#solution-1-hardcode&quot;&gt;Solution 1 - Hardcode&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Count how many times you reference the piece of data. Is it one? What would it look like if you hardcoded that value where you needed it?&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// TwitterLink.js&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;TwitterLink&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://twitter.com/tjaddison&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Twitter&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Even if you reference your Twitter bio in three places on your site, maybe it&#39;s also fine to hardcode in those three places? You&#39;ll almost certainly not be changing your Twitter handle that often (if ever), and if you do change it a find &amp;amp; replace for the full URI (&lt;code&gt;https://twitter.com/tjaddison&lt;/code&gt;) is going to make quick work of it.&lt;/p&gt;
&lt;p&gt;If you&#39;re not building a site to share as a starter, do yourself a favour and keep it simple.&lt;/p&gt;
&lt;h3 id=&quot;solution-2-reference-a-javascript-object&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/09/you-dont-have-to-use-graphql-to-query-your-gatsby-sites-metadata/#solution-2-reference-a-javascript-object&quot;&gt;Solution 2 - Reference a JavaScript object&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you&#39;re going to reference the data in a few places and/or it&#39;s going to change fairly often, then centralizing the data may make sense. Rather than putting the data into Gatsby&#39;s GraphQL layer, we&#39;ll export the values from a file (I used &lt;code&gt;site.config.js&lt;/code&gt;) and then import that in any file that needs it.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// site.config.js&lt;/span&gt;&lt;br /&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;siteMetadata&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;social&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;twitter&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;tjaddison&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// TwitterLink.js&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; React &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; config &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;../../site.config&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;TwitterLink&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;props&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;a&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;https://twitter.com/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;siteMetadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;social&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;twitter&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;      Twitter&lt;br /&gt;    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In addition to needing less code to consume the data, this method is easier to debug as we&#39;re working with plain old JavaScript (&lt;code&gt;console.log(config)&lt;/code&gt; is far easier to work with than debugging the Gatsby GraphQL pipeline).&lt;/p&gt;
&lt;p&gt;In the example above I&#39;ve kept the same nesting (&lt;code&gt;siteMetadata/social/Twitter&lt;/code&gt;) though I could have used any structure (e.g. &lt;code&gt;socialLinks/twitterBioFullUri&lt;/code&gt;).&lt;/p&gt;
&lt;h3 id=&quot;solution-3-hybrid&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/09/you-dont-have-to-use-graphql-to-query-your-gatsby-sites-metadata/#solution-3-hybrid&quot;&gt;Solution 3 - Hybrid&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;One of the strengths of the Gatsby ecosystem is the number of plugins that &lt;em&gt;just work&lt;/em&gt;. Some of them rely on the presence of &lt;code&gt;siteMetadata&lt;/code&gt; int order to &lt;em&gt;just work&lt;/em&gt; and may break if you no longer include that in your &lt;code&gt;gatsby-config.js&lt;/code&gt; (one common plugin that expects &lt;code&gt;siteMetadata&lt;/code&gt; is the RSS plugin &lt;a href=&quot;https://www.gatsbyjs.com/plugins/gatsby-plugin-feed/&quot;&gt;gatsby-plugin-feed&lt;/a&gt;). If you want to keep those plugins working (without needing to reconfigure them) you can re-use your &lt;code&gt;site.config.js&lt;/code&gt; data:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// gatsby-config.js&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; config &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;./site.config&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;siteMetadata&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;siteMetadata&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// rest of the file...&lt;/span&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As long as you include the fields needed by the plugins (&lt;code&gt;gatsby-plugin-feed&lt;/code&gt; needs &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, and &lt;code&gt;siteUrl&lt;/code&gt;) they&#39;ll work just fine.&lt;/p&gt;
&lt;h3 id=&quot;so-no-more-graphql&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/09/you-dont-have-to-use-graphql-to-query-your-gatsby-sites-metadata/#so-no-more-graphql&quot;&gt;So no more GraphQL?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;At least not when there is a better alternative!&lt;/p&gt;
&lt;p&gt;You can&#39;t get very far with Gatsby without having to learn how to work with GraphQL and Gatsby&#39;s implementation. My suggestion is you don&#39;t &lt;em&gt;start&lt;/em&gt; there though, and for simple pieces of data you use the simplest technique that will work. Changing the site title (or your Twitter bio) shouldn&#39;t require people to even see GraphQL, let alone start questioning why it has been used.&lt;/p&gt;
&lt;p&gt;I remember searching &lt;em&gt;really hard&lt;/em&gt; for the &#39;why GraphQL for this data&#39; when first learning Gatsby, and the &lt;a href=&quot;https://www.gatsbyjs.com/docs/why-gatsby-uses-graphql/&quot;&gt;Why Gatsby uses GraphQL&lt;/a&gt; page doesn&#39;t offer much content. I do remember reading:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;GraphQL is certainly not required, but the benefits of adopting GraphQL are significant. GraphQL will simplify the process of building and optimizing your pages, so it’s considered a best practice for structuring and writing Gatsby applications.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Based on that I figured I better just go ahead and learn it. Older (maybe wiser?) me now looks at that document as a bit of marketing copy rather than a great assessment of the pros/cons of using GraphQL vs. other methods to populate data on your site. I&#39;d also question what aspects of building the site it simplifies for most use cases! After teaching Gatsby a few times I&#39;m now convinced the pedagogical value of having people deal with the GraphQL concepts just to get their name on a starter...definitely too much.&lt;/p&gt;
&lt;p&gt;Speaking of starters - if the prevalence (nigh-exclusivity) of GraphQL in the docs is the genesis of this issue, then the starter library is what boosted this problem into the stratosphere.&lt;/p&gt;
&lt;h3 id=&quot;gatsby-starters&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/09/you-dont-have-to-use-graphql-to-query-your-gatsby-sites-metadata/#gatsby-starters&quot;&gt;Gatsby starters&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The first iteration of my site was a mess (it still is). It worked though, and I could have shared it for others to build on. I would have been sharing cobbled together bits of code with multiple solutions to a single problem (e.g. sourcing data). There were edge cases I&#39;d worked around and not documented, and packages included that were no longer required. As something to &lt;em&gt;learn&lt;/em&gt; from it would have been a poor starting point.&lt;/p&gt;
&lt;p&gt;And that is basically the experience I had when learning Gatsby - after completing the tutorial I picked a few different starters (either official or popular community submissions) and set about reverse engineering the &#39;why&#39; of various technical decisions that were present in a &#39;good&#39; Gatsby site.&lt;/p&gt;
&lt;p&gt;This was not particularly helpful 😅.&lt;/p&gt;
&lt;p&gt;My advice to people learning Gatsby today is to really vet any starter before committing to build on it (lest they find themselves with a partly TypeScript, partly styled-components, partly inline-CSS...&#39;unique&#39; starter). If something looks overcomplicated (in docs, blog posts, starters) then &lt;em&gt;it probably is&lt;/em&gt;, consider how you&#39;d solve the problem without Gatsby and see if that is any easier. I&#39;m hopeful this situation will improve over time, otherwise it&#39;ll be hard to even suggest Gatsby for technical blogs (over something like &lt;a href=&quot;https://pages.github.com/&quot;&gt;GitHub pages&lt;/a&gt; or &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;11ty&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;If you want to explore more reading on the &#39;Gatsby is overkill&#39; theme, I highly recommend the post by Jared Palmer that &lt;a href=&quot;https://jaredpalmer.com/gatsby-vs-nextjs&quot;&gt;takes these ideas a little further&lt;/a&gt; (I&#39;d like to point out I still think Gatsby is great - but I can see how it&#39;s not always the right fit for everyone).&lt;/p&gt;
</description>
      <pubDate>Wed, 30 Sep 2020 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2020/09/you-dont-have-to-use-graphql-to-query-your-gatsby-sites-metadata/</guid>
    </item>
    <item>
      <title>Updating to Tailwind Typography to style markdown posts</title>
      <link>https://tjaddison.com/blog/2020/08/updating-to-tailwind-typography-to-style-markdown-posts/</link>
      <description>&lt;p&gt;Last year I shared some CSS to &lt;a href=&quot;https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/&quot;&gt;style your markdown posts with Tailwind CSS&lt;/a&gt;. Earlier this year a &lt;a href=&quot;https://tailwindcss.com/docs/typography-plugin&quot;&gt;Typography plugin&lt;/a&gt; was released for &lt;a href=&quot;https://tailwindcss.com/&quot;&gt;Tailwind CSS&lt;/a&gt; that takes care of styling your vanilla HTML for you (the kind generated by GatsbyJS processing markdown files). This post will walk through the process of migrating from a custom stylesheet to the &lt;a href=&quot;https://tailwindcss.com/docs/typography-plugin&quot;&gt;Typography plugin&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To get started I installed the plugin (see the &lt;a href=&quot;https://github.com/tailwindlabs/tailwindcss-typography#installation&quot;&gt;installation instructions on GitHub&lt;/a&gt;):&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;yarn&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; @tailwindcss/typography&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# or by adding the tailwind UI plugin (which includes tailwind typography)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;yarn&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; @tailwindcss/ui&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And add to the plugin array in &lt;code&gt;tailwind.config.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;  &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;@tailwindcss/typography&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// Or if you use tailwind UI&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;@tailwindcss/ui&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then update the class on the element that will contain the rendered markdown:&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; &amp;lt;article className=&quot;markdown&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; &amp;lt;article className=&quot;prose prose-lg&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And done! There were a few differences between my &lt;a href=&quot;https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/&quot;&gt;old stylesheet&lt;/a&gt; and the new plugin:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Different default text color (&lt;code&gt;bg-gray-700&lt;/code&gt; instead of &lt;code&gt;bg-gray-800&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Different link styling (gray with underline - far too edgy for me!)&lt;/li&gt;
&lt;li&gt;Added quotes around &lt;code&gt;inline code&lt;/code&gt; and blockquotes&lt;/li&gt;
&lt;li&gt;More spacing around headers and between lines&lt;/li&gt;
&lt;li&gt;A lower max-width for the container&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I was pleasantly surprised to discover that the site didn&#39;t look all that different after switching over - my stylesheet can&#39;t have been that bad from a design perspective.&lt;/p&gt;
&lt;p&gt;Some of the changes I&#39;ve decided to stick with - I&#39;ve adopted &lt;code&gt;bg-gray-700&lt;/code&gt; globally as well as the enhanced spacing. The other items though (link styling, quotes, container width) I wanted to undo some of the additions. Fortunately the &lt;a href=&quot;https://github.com/tailwindlabs/tailwindcss-typography&quot;&gt;documentation on Github&lt;/a&gt; made this a mostly painless experience.&lt;/p&gt;
&lt;p&gt;Updating container width was really easy to update:&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; &amp;lt;article className=&quot;prose prose-lg&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt; &amp;lt;article className=&quot;prose prose-lg max-w-none&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Changing the default link styling required adding a &lt;code&gt;typography&lt;/code&gt; key to the &lt;code&gt;tailwind.config.js&lt;/code&gt; file and overriding the default &lt;code&gt;a&lt;/code&gt; styling:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token function-variable function&quot;&gt;typography&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;theme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;css&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token literal-property property&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token literal-property property&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;theme&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;colors.blue.600&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token literal-property property&quot;&gt;textDecoration&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;none&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token string-property property&quot;&gt;&quot;&amp;amp;:hover&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token literal-property property&quot;&gt;textDecoration&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;underline&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Although it looks like a lot of code most of it is boilerplate, and the actual styling overrides are only a few lines. The lines above gave me back my blue links with that all-important underline-on-hover (which maybe qualifies as retro in 2020?).&lt;/p&gt;
&lt;p&gt;The final change was to disable the additional quotes (which use before/after content) - what I used was the following addition to the &lt;code&gt;css&lt;/code&gt; key in the config:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// in the css: { ...block ... }&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string-property property&quot;&gt;&quot;code::before&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string-property property&quot;&gt;&quot;code::after&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string-property property&quot;&gt;&quot;blockquote p:first-of-type::before&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token string-property property&quot;&gt;&quot;blockquote p:last-of-type::after&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we&#39;re &lt;em&gt;almost&lt;/em&gt; done. The one piece of custom styling I had to preserve was the rule that stops &lt;code&gt;prismjs&lt;/code&gt; from causing &lt;code&gt;extremely long pieces of inline code (like this one here)&lt;/code&gt; to scroll rather than wrap (now targeting &lt;code&gt;prose&lt;/code&gt;, rather than my old &lt;code&gt;markdown&lt;/code&gt; class):&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;.prose :not(pre) &gt; code.language-text&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;white-space&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; pre-line&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Overall I&#39;m really happy to be building on a design foundation that will evolve as Tailwind CSS does. It also allowed me to replace about 100 lines of custom CSS with 20 lines of tailwind overrides.&lt;/p&gt;
</description>
      <pubDate>Mon, 31 Aug 2020 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2020/08/updating-to-tailwind-typography-to-style-markdown-posts/</guid>
    </item>
    <item>
      <title>Using GistPad to manage your GitHub digital gardens</title>
      <link>https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/</link>
      <description>&lt;p&gt;I love &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=vsls-contrib.gistfs&quot;&gt;GistPad&lt;/a&gt; - it has redefined how I think about working with a markdown based wiki in GitHub. &lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;VSCode&lt;/a&gt; is my preferred editor for markdown - adding the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=vsls-contrib.gistfs&quot;&gt;Gistpad extension&lt;/a&gt; takes it to the next level.&lt;/p&gt;
&lt;h2 id=&quot;from-work-journal-to-digital-garden&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/#from-work-journal-to-digital-garden&quot;&gt;From work journal to digital garden&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I previously wrote about &lt;a href=&quot;https://tjaddison.com/blog/2018/02/keeping-a-work-journal-with-vs-code/&quot;&gt;keeping a work journal with VSCode&lt;/a&gt;, and while it was OK, the friction of git (pull, commit, push) really broke the flow. Once I started exploring other knowledge-management systems (see &lt;a href=&quot;https://github.com/brettkromkamp/awesome-knowledge-management&quot;&gt;awesome knowledge management&lt;/a&gt;) I realized what I was really after was a &lt;a href=&quot;https://maggieappleton.com/garden-history&quot;&gt;digital garden&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The ability to link between notes (pages, files, etc.) is key - and so the manual work log didn&#39;t scale very far. Browser-based systems like &lt;a href=&quot;https://www.onenote.com/&quot;&gt;OneNote&lt;/a&gt; and &lt;a href=&quot;https://roamresearch.com/&quot;&gt;Roam Research&lt;/a&gt; are closer but have other limitations (price, not markdown, a lot of ceremony in splitting by personal/work/client, etc.). &lt;a href=&quot;https://github.com/foambubble/foam&quot;&gt;Foam&lt;/a&gt; is an exciting newcomer, but still leaves you to manage the &lt;code&gt;git&lt;/code&gt; work yourself.&lt;/p&gt;
&lt;p&gt;The digital gardens I have (and continue to have) the most success with are actually in &lt;a href=&quot;https://www.onenote.com/&quot;&gt;OneNote&lt;/a&gt; (once configured it&#39;s just so easy to access, share, cross-link). As they were mostly private (family or work related) the lack of an easy way to share publicly wasn&#39;t really an issue.&lt;/p&gt;
&lt;p&gt;Recently I&#39;ve wanted to start &lt;a href=&quot;https://notes.andymatuschak.org/z21cgR9K3UcQ5a7yPsj2RUim3oM2TzdBByZu&quot;&gt;working with the garage door up&lt;/a&gt;, and after randomly seeing a tweet on &lt;a href=&quot;https://twitter.com/LostInTangent/status/1282047676377231360&quot;&gt;using GistPad for knowledge bases&lt;/a&gt; the last thing holding me back (that damned &lt;code&gt;git&lt;/code&gt; workflow!) was no longer an excuse. Thank you to whatever Twitter algorithm put that tweet into my feed!&lt;/p&gt;
&lt;p&gt;It&#39;s only been a few weeks but I&#39;m totally hooked on GistPad and cannot imagine this workflow without it. &lt;a href=&quot;https://github.com/vsls-contrib/gistpad#repositories&quot;&gt;Managing repos&lt;/a&gt; is only one of &lt;em&gt;many&lt;/em&gt; features - and some of them could easily be extensions in their own right (e.g. managing gists, code tours, playgrounds). I&#39;d highly recommend browsing the &lt;a href=&quot;https://github.com/vsls-contrib/gistpad#readme&quot;&gt;readme&lt;/a&gt; and looking at the demos of what it can do.&lt;/p&gt;
&lt;p&gt;If you just want to start managing (or just start!) your digital garden, it&#39;s only a few steps...&lt;/p&gt;
&lt;h2 id=&quot;managing-a-repo&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/#managing-a-repo&quot;&gt;Managing a repo&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Get started by installing the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=vsls-contrib.gistfs&quot;&gt;GistPad&lt;/a&gt; extension. To configure it you&#39;ll need to provide a GitHub personal access token with the &lt;code&gt;repo&lt;/code&gt; permission, which you can create on the &lt;a href=&quot;https://github.com/settings/tokens/new&quot;&gt;new token&lt;/a&gt; page.&lt;/p&gt;
&lt;p&gt;By default you&#39;ll see all of your Gists and a Showcase - but what we&#39;re after is a repo.&lt;/p&gt;
&lt;p&gt;Open the command palette and run &lt;code&gt;GistPad: Manage Repository&lt;/code&gt;. This will give you a list of all your repos, as well as the chance to create a public or private repo.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/0rpf7n6IL9-295.avif 295w, https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/0rpf7n6IL9-590.avif 590w, https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/0rpf7n6IL9-885.avif 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/0rpf7n6IL9-295.webp 295w, https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/0rpf7n6IL9-590.webp 590w, https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/0rpf7n6IL9-885.webp 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/0rpf7n6IL9-295.jpeg 295w, https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/0rpf7n6IL9-590.jpeg 590w, https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/0rpf7n6IL9-885.jpeg 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Create a repo&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/0rpf7n6IL9-295.jpeg&quot; width=&quot;885&quot; height=&quot;227&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;I created a public one and called it &lt;code&gt;garage-wiki&lt;/code&gt;, as any repo with &lt;code&gt;wiki&lt;/code&gt; in the name will get special wiki treatment from &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=vsls-contrib.gistfs&quot;&gt;GistPad&lt;/a&gt;. One of the most significant changes you&#39;ll get by being in wiki mode is that you can use &lt;code&gt;[[double brackets]]&lt;/code&gt; to create internal links, and &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=vsls-contrib.gistfs&quot;&gt;GistPad&lt;/a&gt; will also maintain &lt;em&gt;backlinks&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;An example of how those backlinks show up is below - you can see that I&#39;ve linked to my webmentions page from three different daily journal entries.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/x6NXGNYVvZ-295.avif 295w, https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/x6NXGNYVvZ-590.avif 590w, https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/x6NXGNYVvZ-885.avif 885w, https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/x6NXGNYVvZ-1180.avif 1180w, https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/x6NXGNYVvZ-1475.avif 1475w, https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/x6NXGNYVvZ-1770.avif 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/x6NXGNYVvZ-295.webp 295w, https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/x6NXGNYVvZ-590.webp 590w, https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/x6NXGNYVvZ-885.webp 885w, https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/x6NXGNYVvZ-1180.webp 1180w, https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/x6NXGNYVvZ-1475.webp 1475w, https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/x6NXGNYVvZ-1770.webp 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/x6NXGNYVvZ-295.jpeg 295w, https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/x6NXGNYVvZ-590.jpeg 590w, https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/x6NXGNYVvZ-885.jpeg 885w, https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/x6NXGNYVvZ-1180.jpeg 1180w, https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/x6NXGNYVvZ-1475.jpeg 1475w, https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/x6NXGNYVvZ-1770.jpeg 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Example of backlinks&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/x6NXGNYVvZ-295.jpeg&quot; width=&quot;1770&quot; height=&quot;910&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;And now you&#39;re done - you can create, edit, and delete files in the repo - and as soon as you hit save the changes will be synced back to GitHub. No add/commit/push required.&lt;/p&gt;
&lt;p&gt;If you&#39;re into journalling you can click the calendar icon next to your repo and it&#39;ll create a new page for the current day.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/xTXa8gbZ55-295.avif 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/xTXa8gbZ55-295.webp 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Daily journal button&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/xTXa8gbZ55-295.jpeg&quot; width=&quot;295&quot; height=&quot;274&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Right now the extension is getting updates all the time, so I would strongly recommend you browse the relevant section of the &lt;a href=&quot;https://github.com/vsls-contrib/gistpad#readme&quot;&gt;readme&lt;/a&gt;. The ongoing updates around support for &lt;a href=&quot;https://github.com/foambubble/foam&quot;&gt;Foam&lt;/a&gt; are particularly interesting if you&#39;re thinking about building your digital garden/personal knowledge base.&lt;/p&gt;
&lt;p&gt;In closing, I 😍 &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=vsls-contrib.gistfs&quot;&gt;GistPad&lt;/a&gt; - make sure you give it a try!&lt;/p&gt;
</description>
      <pubDate>Fri, 31 Jul 2020 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2020/07/using-gistpad-to-manage-your-github-digital-gardens/</guid>
    </item>
    <item>
      <title>Password protect a Next.js site hosted on Vercel</title>
      <link>https://tjaddison.com/blog/2020/06/password-protect-a-nextjs-site-deployed-to-vercel/</link>
      <description>&lt;p&gt;Want to password-protect some or all of your &lt;a href=&quot;https://nextjs.org/&quot;&gt;Next.js&lt;/a&gt; site, but want to stick with a free-tier hosting plan? This blog post will walk through the steps to add the following lightweight protection to your deployment:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2020/06/password-protect-a-nextjs-site-deployed-to-vercel/GuX8SDs4vo-295.avif 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2020/06/password-protect-a-nextjs-site-deployed-to-vercel/GuX8SDs4vo-295.webp 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Login&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2020/06/password-protect-a-nextjs-site-deployed-to-vercel/GuX8SDs4vo-295.jpeg&quot; width=&quot;295&quot; height=&quot;97&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;If you&#39;d like to jump straight to the source code there is an &lt;a href=&quot;https://github.com/taddison/next-password-protect-sample&quot;&gt;example repo on Github&lt;/a&gt; which is also deployed to Vercel. See the &lt;a href=&quot;https://next-password-protect-sample.vercel.app/&quot;&gt;public page&lt;/a&gt; as well as a &lt;a href=&quot;https://next-password-protect-sample.vercel.app/protected&quot;&gt;protected page&lt;/a&gt; (the password is &lt;code&gt;letmein&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id=&quot;why-roll-our-own-password-protection&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/06/password-protect-a-nextjs-site-deployed-to-vercel/#why-roll-our-own-password-protection&quot;&gt;Why roll our own password protection?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In a few cases I&#39;ve wanted to protect one or more pages on a site, but haven&#39;t had any need (or desire!) to integrate an authentication/authentication framework.&lt;/p&gt;
&lt;p&gt;Adding password protection with a hosting provider is possible, but expensive. &lt;a href=&quot;http://surge.sh/pricing&quot;&gt;Surge&lt;/a&gt; requires you to be on a $30/month plan, &lt;a href=&quot;https://www.netlify.com/pricing/&quot;&gt;Netlify&lt;/a&gt; needs the $45/month plan, and with &lt;a href=&quot;https://vercel.com/blog/protecting-deployments&quot;&gt;Vercel&lt;/a&gt; it&#39;s a $150/month addon to the pro plan (which is already $20/month).&lt;/p&gt;
&lt;p&gt;You obviously get a lot more for your money than just password protection, and I&#39;d hope their implementations are more robust than what you&#39;ll find here (though I couldn&#39;t find any documentation on the password protection implementation).&lt;/p&gt;
&lt;p&gt;Some notable issues with what we&#39;ll build today are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No rate limiting&lt;/li&gt;
&lt;li&gt;No Captcha&lt;/li&gt;
&lt;li&gt;Password stored in cleartext in a cookie&lt;/li&gt;
&lt;li&gt;No protection for static files (in &lt;code&gt;public&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Requires opting-in on a per-page, per-api-route basis&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I definitely wouldn&#39;t put anything sensitive behind this protection - I typically use it to protect early proof of concepts or as a placeholder for authentication.&lt;/p&gt;
&lt;p&gt;And finally, implementing this will disable Next&#39;s &lt;a href=&quot;https://nextjs.org/docs/advanced-features/automatic-static-optimization&quot;&gt;automatic static optimization&lt;/a&gt; as every page will need some server-side logic (checking if your password cookie is correct). If you were wondering why there aren&#39;t many (any?) hosting providers that support password protection on the free tier, this might help explain why.&lt;/p&gt;
&lt;h2 id=&quot;getting-started&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/06/password-protect-a-nextjs-site-deployed-to-vercel/#getting-started&quot;&gt;Getting started&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can either start from a blank Next app, or use one of the templates below to get up and running with a starter that has &lt;a href=&quot;https://tailwindcss.com/&quot;&gt;Tailwind CSS&lt;/a&gt;, ESLint, and Prettier, and optionally TypeScript. Note this post will assume you&#39;re using the JavaScript example.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Vanilla&lt;/span&gt;&lt;br /&gt;npx create-next-app next-password-protected&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# or Javascript&lt;/span&gt;&lt;br /&gt;npx create-next-app next-password-protected &lt;span class=&quot;token parameter variable&quot;&gt;--example&lt;/span&gt; https://github.com/aedificatorum/next-starters/tree/main/tailwind-js&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# or TypeScript&lt;/span&gt;&lt;br /&gt;npx create-next-app next-password-protected &lt;span class=&quot;token parameter variable&quot;&gt;--example&lt;/span&gt; https://github.com/aedificatorum/next-starters/tree/main/tailwind&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then run it:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; next-password-protect&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;yarn&lt;/span&gt; dev&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# browse to http://localhost:3000&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our goals are to take this app and:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Allow anyone to visit the main page (&lt;code&gt;/&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Add a protected page (&lt;code&gt;/protected&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Add a login/logout page (&lt;code&gt;/login&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;creating-a-protected-page&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/06/password-protect-a-nextjs-site-deployed-to-vercel/#creating-a-protected-page&quot;&gt;Creating a protected page&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Create a new &lt;code&gt;protected.js&lt;/code&gt; in the &lt;code&gt;pages&lt;/code&gt; folder, and add the following code:&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// /pages/protected.js&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Head &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/head&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Protected&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; hasReadPermission &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;hasReadPermission&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Access denied.&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Head&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Protected Page&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Head&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;&lt;br /&gt;      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;I am supposed to be protected.&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you browse to &lt;code&gt;/protected&lt;/code&gt; you&#39;ll now get the access denied message.&lt;/p&gt;
&lt;h2 id=&quot;injecting-the-hasreadpermission-prop&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/06/password-protect-a-nextjs-site-deployed-to-vercel/#injecting-the-hasreadpermission-prop&quot;&gt;Injecting the hasReadPermission prop&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We&#39;re going to handle setting this prop in the &lt;code&gt;_app.js&lt;/code&gt; file, which you&#39;ll need to add to the &lt;code&gt;pages&lt;/code&gt; folder. This leverages the &lt;a href=&quot;https://nextjs.org/docs/advanced-features/custom-app&quot;&gt;custom app&lt;/a&gt; feature of Next, and will run this logic for every page.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// /pages/app.js&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; App &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/app&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;MyApp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Component&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pageProps &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Component &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;pageProps&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;MyApp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;getInitialProps&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;appContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; appProps &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; App&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getInitialProps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;appContext&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    appProps&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pageProps&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hasReadPermission &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;appProps &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; MyApp&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In our initial implementation we&#39;re going to give you a 50% chance of viewing the page on each refresh. Try it a couple of times, and you should get to see both access denied, and the protected content.&lt;/p&gt;
&lt;h2 id=&quot;protecting-with-a-cookie&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/06/password-protect-a-nextjs-site-deployed-to-vercel/#protecting-with-a-cookie&quot;&gt;Protecting with a cookie&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Rather than roll the dice on every refresh, we&#39;ll now check a cookie on each request. To do so we&#39;ll use the &lt;a href=&quot;https://www.npmjs.com/package/universal-cookie&quot;&gt;universal cookie&lt;/a&gt; package, which lets us work with cookies in both node and the browser with one package/API:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;yarn&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; universal-cookie&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&#39;ll put our cookie name in a &lt;code&gt;consts.js&lt;/code&gt; file (debugging cookie name typos is great fun, but we&#39;ll skip that step today):&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// /consts.js&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;SiteReadCookie&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;src&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And now we&#39;ll check this cookie to see if the client has the right password, by updating &lt;code&gt;_app.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// /pages/app.js&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// At the top of _app.js&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Cookies &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;universal-cookie&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; consts &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;consts&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// In the getInitialProps function, instead of our &#39;random&#39; protection&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cookies &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Cookies&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;appContext&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ctx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cookie&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; password &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cookies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SiteReadCookie&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;password &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;letmein&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  appProps&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pageProps&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;hasReadPermission &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;My consts file is in the root folder (not the pages folder), though I&#39;ve got &lt;a href=&quot;https://tjaddison.com/blog/2020/04/absolute-imports-with-react/&quot;&gt;absolute imports&lt;/a&gt; configured, so no need to import from &lt;code&gt;../consts&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you check the &lt;code&gt;/protected&lt;/code&gt; page you&#39;ll now see you can&#39;t access it any more. If you add a cookie with the right password (letmein) in the dev tools you&#39;ll be able to get in again.&lt;/p&gt;
&lt;p&gt;Keep in mind we&#39;ve put the password in the source code, so if your source isn&#39;t private this is a bit of security theatre. If the source is going to be public you could use an environment variable instead.&lt;/p&gt;
&lt;h2 id=&quot;adding-password-entry&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/06/password-protect-a-nextjs-site-deployed-to-vercel/#adding-password-entry&quot;&gt;Adding password entry&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In this case &#39;logging in&#39; means setting a cookie to the password value. To do this we&#39;ll create a login component, and then replace the &#39;Access Denied&#39; view on the page with the login component.&lt;/p&gt;
&lt;p&gt;To do that first create a login component. Note the below is styled with &lt;a href=&quot;https://tailwindcss.com/&quot;&gt;Tailwind CSS&lt;/a&gt; with the &lt;a href=&quot;https://www.npmjs.com/package/@tailwindcss/ui&quot;&gt;tailwind ui&lt;/a&gt; package for &lt;a href=&quot;https://www.npmjs.com/package/@tailwindcss/custom-forms&quot;&gt;custom forms&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// /components/login.js&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; useState &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Cookies &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;universal-cookie&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; consts &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;consts&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;Login&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; redirectPath &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;password&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setPassword&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;w-1/3 max-w-sm mx-auto&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;form&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;label&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;block&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;          &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;span&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;text-gray-700&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Password&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;span&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;          &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;input&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;form-input mt-1 block w-full bg-gray-50&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token attr-name&quot;&gt;placeholder&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;Your site password&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;password&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token attr-name&quot;&gt;onChange&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setPassword&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;submit&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;mt-3 bg-green-400 text-white p-2 font-bold rounded hover:bg-green-600&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token attr-name&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cookies &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Cookies&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;            cookies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SiteReadCookie&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; password&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;              &lt;span class=&quot;token literal-property property&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;            window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; redirectPath &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;          Login&lt;br /&gt;        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;form&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; Login&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our component accepts a single optional prop (&lt;code&gt;redirectPath&lt;/code&gt;) which allows us to redirect the client after they set the cookie. Note that we use &lt;code&gt;window.location.href&lt;/code&gt; as the redirect &lt;em&gt;must&lt;/em&gt; trigger a request to the server, and we don&#39;t want Next to attempt to use client-side routing.&lt;/p&gt;
&lt;p&gt;We can now add our login component to our protected page, and render the Login component if the client doesn&#39;t have permission to see the page. This means they either don&#39;t have the cookie, or they have the cookie but the password is wrong.&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// /pages/protected.js&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// At the top of protected.js&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; useRouter &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/router&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Login &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;components/Login&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Inside our function component&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; router &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;useRouter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;hasReadPermission&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Login&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;redirectPath&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;router&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;asPath&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you refresh the page you can now try setting the wrong password, or the right password.&lt;/p&gt;
&lt;h2 id=&quot;adding-a-logout-page&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/06/password-protect-a-nextjs-site-deployed-to-vercel/#adding-a-logout-page&quot;&gt;Adding a logout page&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Right now the only way to &#39;logout&#39; is to clear your cookies, and while testing you&#39;ll appreciate having a logout button.&lt;/p&gt;
&lt;p&gt;To solve this we&#39;ll add a &lt;code&gt;login&lt;/code&gt; page in the &lt;code&gt;pages&lt;/code&gt; folder which will allow people to login and logout:&lt;/p&gt;
&lt;pre class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Head &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;next/head&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Cookies &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;universal-cookie&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Login &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Components/Login&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; consts &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;consts&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;LoginPage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; hasReadPermission &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hasReadPermission&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Head&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;          &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Logout&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Head&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;w-1/3 max-w-sm mx-auto&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;          &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token attr-name&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;mt-3 bg-green-400 text-white p-2 font-bold rounded hover:bg-green-600&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token attr-name&quot;&gt;onClick&lt;/span&gt;&lt;span class=&quot;token script language-javascript&quot;&gt;&lt;span class=&quot;token script-punctuation punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;              e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;preventDefault&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;              &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cookies &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Cookies&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;              cookies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SiteReadCookie&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;              window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/login&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;            Logout&lt;br /&gt;          &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Head&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;        &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;Login&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Head&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;      &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Login&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;redirectPath&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token plain-text&quot;&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you browse to &lt;code&gt;/login&lt;/code&gt; you&#39;ll now be able to login or logout.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/06/password-protect-a-nextjs-site-deployed-to-vercel/#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You&#39;ve now got basic password protection functionality deployed. Keep in mind that you must add this protection manually to every page you don&#39;t want to be public. If you&#39;d like to protect the whole site without messing around with the prop on each page you could handle that in the &lt;code&gt;_app.js&lt;/code&gt;, redirecting to a login endpoint or returning a 403.&lt;/p&gt;
&lt;p&gt;Even with blanket protection in your &lt;code&gt;_app.js&lt;/code&gt; keep in mind that your API routes will need protecting individually, as &lt;code&gt;_app.js&lt;/code&gt; only executes for pages.&lt;/p&gt;
&lt;p&gt;Hope this was helpful and/or educational!&lt;/p&gt;
</description>
      <pubDate>Tue, 30 Jun 2020 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2020/06/password-protect-a-nextjs-site-deployed-to-vercel/</guid>
    </item>
    <item>
      <title>Tips for 1 on 1s during COVID-19</title>
      <link>https://tjaddison.com/blog/2020/05/tips-for-1-on-1s-during-COVID-10/</link>
      <description>&lt;p&gt;We&#39;re now at around week 10 of COVID-19 in NYC, and it looks like it&#39;ll be a major part of 2020, if not beyond. While the business of &lt;em&gt;business&lt;/em&gt; still moves forward, I&#39;ve found that my 1:1s have been more demanding than ever before. I&#39;m lucky to have a great team who have provided me with plenty of support, and I&#39;d like to share some practices that have guided how I&#39;ve approached 1:1s.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Most of this advice is generic and applies to all 1:1s, even in non-crisis times.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;be-vulnerable&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/05/tips-for-1-on-1s-during-COVID-10/#be-vulnerable&quot;&gt;Be vulnerable&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It&#39;s vanishingly unlikely none of your team are concerned. It&#39;s also unlikely that they&#39;re likely to bring anything up if you don&#39;t allow them to see your own concerns too.&lt;/p&gt;
&lt;p&gt;Being able to share that you are concerned about the future, that you don&#39;t have all the answers, and that you are looking for support from your team (in addition to providing it) - it all goes a long way towards creating an environment where people can confide in you.&lt;/p&gt;
&lt;h2 id=&quot;be-supportive-of-problems&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/05/tips-for-1-on-1s-during-COVID-10/#be-supportive-of-problems&quot;&gt;Be supportive of problems&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You&#39;ve probably heard the phrase &lt;em&gt;&#39;don&#39;t bring me problems, bring me solutions&#39;&lt;/em&gt;. It has to be some of the worst advice I&#39;ve ever heard - I think the more honest form of this advice is &lt;em&gt;&#39;only bring me trivial problems that you&#39;ve solved, let the systemic ones fester&#39;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Concern about the future state of society is about as unsolvable as it gets. If you&#39;ve not created the space where your team feel that they can share concerns or unsolved problems, then you&#39;ve got no chance here.&lt;/p&gt;
&lt;p&gt;Trying to create some weird divide where &lt;em&gt;personal problems&lt;/em&gt; get different treatment to &lt;em&gt;business problems&lt;/em&gt; dehumanizes people. Don&#39;t do it.&lt;/p&gt;
&lt;h2 id=&quot;address-privilege&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/05/tips-for-1-on-1s-during-COVID-10/#address-privilege&quot;&gt;Address privilege&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We&#39;re sitting having a 1:1 in the middle of a global pandemic that we can barely get our heads around. We&#39;re both very privileged to still have a job. It&#39;s ok to address that, be thankful, and still raise concerns.&lt;/p&gt;
&lt;p&gt;Does it sometimes feel absurd to see what is happening in the world and then bring up an issue with some mundane (but annoying) expense policy? Of course it does! That&#39;s ok, and calling it out can help people move past that (or at least acknowledge it).&lt;/p&gt;
&lt;h2 id=&quot;create-deliberate-space-to-address-the-unfixable&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/05/tips-for-1-on-1s-during-COVID-10/#create-deliberate-space-to-address-the-unfixable&quot;&gt;Create deliberate space to address the unfixable&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It&#39;s great when you&#39;re able to help someone. Maybe you come up with a way to level-up someone&#39;s career, or figure out the perfect team to maximize the chance of success on a tough project. Falling into the &#39;debug/fix it&#39; trap is an easy mistake to make as a manager, and there is a whole lot going on right now that cannot be fixed.&lt;/p&gt;
&lt;p&gt;Rather than ignoring that bucket, I suggest you address it.&lt;/p&gt;
&lt;p&gt;You probably won’t hear someone say ‘there is an unfixable problem I want to talk about’, but you might hear &#39;I don’t feel productive and it’s bothering me&#39;. Listen for signs that your team wants to talk, and remind yourself that they (and you) are still grappling with the fact that this isn’t just remote work, this is remote work &lt;em&gt;in a pandemic&lt;/em&gt;. It helps to mention that, a lot.&lt;/p&gt;
&lt;p&gt;Acknowledging the feelings/reality that things aren&#39;t the same as they used to be is a conversation worth having, and you should underline that you don&#39;t expect these issues to be fixed/go away. Try: &lt;em&gt;It&#39;s ok to struggle with these things, I&#39;m not expecting anything more from you there - you&#39;re doing a great job.&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Some of us have got 10/15/20+ years of expertise in navigating working in an office, and after 10 weeks (less than 1%!) we&#39;re wondering why things are harder than they used to be.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;revise-your-schedule&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/05/tips-for-1-on-1s-during-COVID-10/#revise-your-schedule&quot;&gt;Revise your schedule&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you&#39;re not meeting weekly with your team right now, consider upping the frequency. This is probably the most disruptive period of many people&#39;s lives, and if you are catching up with them every fortnight, month, or quarter - you owe them more than that.&lt;/p&gt;
&lt;p&gt;You can always cut a meeting short, and dial back the schedule if you both agree it&#39;s superfluous. It&#39;s not always easy to ask your manager for more time, so do your team the favour of offering it first.&lt;/p&gt;
&lt;h2 id=&quot;rome-wasnt-built-in-a-day&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/05/tips-for-1-on-1s-during-COVID-10/#rome-wasnt-built-in-a-day&quot;&gt;Rome wasn&#39;t built in a day...&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;...and if you attempt everything above in a single meeting, it&#39;s going to be a &lt;em&gt;really heavy 1:1&lt;/em&gt;! The stronger the relationship with your team, the easier navigating these kind of topics will be.&lt;/p&gt;
&lt;p&gt;If you&#39;ve not addressed the pandemic and it&#39;s impact yet, your next 1:1 is the perfect time to get started.&lt;/p&gt;
&lt;p&gt;Stay safe.&lt;/p&gt;
</description>
      <pubDate>Sun, 31 May 2020 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2020/05/tips-for-1-on-1s-during-COVID-10/</guid>
    </item>
    <item>
      <title>Absolute imports with React</title>
      <link>https://tjaddison.com/blog/2020/04/absolute-imports-with-react/</link>
      <description>&lt;p&gt;I&#39;d like to say that all my time writing relative import statements like this has taught me some valuable lessons:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Gauge &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;../../../components/Gauge&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, I can safely say that if I could go back in time and send myself a link to this post that all I&#39;d be sacrificing is a bunch of frustration, and instead of the above I&#39;d be able to write:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Gauge &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;components/Gauge&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It also means that VS Code&#39;s helpful attempts (which for me have had mixed results) at fixing imports when moving files around becomes a thing of the past.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2020/04/absolute-imports-with-react/d7K_gZMPxL-295.avif 295w, https://tjaddison.com/blog/2020/04/absolute-imports-with-react/d7K_gZMPxL-590.avif 590w, https://tjaddison.com/blog/2020/04/absolute-imports-with-react/d7K_gZMPxL-885.avif 885w, https://tjaddison.com/blog/2020/04/absolute-imports-with-react/d7K_gZMPxL-1180.avif 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2020/04/absolute-imports-with-react/d7K_gZMPxL-295.webp 295w, https://tjaddison.com/blog/2020/04/absolute-imports-with-react/d7K_gZMPxL-590.webp 590w, https://tjaddison.com/blog/2020/04/absolute-imports-with-react/d7K_gZMPxL-885.webp 885w, https://tjaddison.com/blog/2020/04/absolute-imports-with-react/d7K_gZMPxL-1180.webp 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2020/04/absolute-imports-with-react/d7K_gZMPxL-295.jpeg 295w, https://tjaddison.com/blog/2020/04/absolute-imports-with-react/d7K_gZMPxL-590.jpeg 590w, https://tjaddison.com/blog/2020/04/absolute-imports-with-react/d7K_gZMPxL-885.jpeg 885w, https://tjaddison.com/blog/2020/04/absolute-imports-with-react/d7K_gZMPxL-1180.jpeg 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;VS Code updating imports&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2020/04/absolute-imports-with-react/d7K_gZMPxL-295.jpeg&quot; width=&quot;1180&quot; height=&quot;212&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;The good news is that this is a very easy fix, as both create-react-app (CRA) and Next.js (Next) will read from &lt;code&gt;jsconfig.json&lt;/code&gt; (if it exists) during the build process.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;To learn more about the &lt;code&gt;jsconfig.json&lt;/code&gt; file, including other benefits it proves, configuration options, and best practices, check the &lt;a href=&quot;https://code.visualstudio.com/docs/languages/jsconfig&quot;&gt;Visual Studio Code documentation on jsconfig&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The file needs to go in the root of your project folder, and the contents vary based on whether you&#39;re in a CRA or Next project:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// jsconfig.json for create-react-app&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;compilerOptions&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;baseUrl&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;src&quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;include&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;src&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// jsconfig.json for Next.js&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;compilerOptions&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;baseUrl&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;exclude&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;node_modules&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key difference is which folder is the base - for Next it&#39;s the root (&lt;code&gt;.&lt;/code&gt;) folder, and for CRA it&#39;s the &lt;code&gt;src&lt;/code&gt; folder.&lt;/p&gt;
&lt;p&gt;We also want to ensure we exclude &lt;code&gt;node_modules&lt;/code&gt;, which is explicit for Next (&lt;code&gt;&amp;quot;exclude&amp;quot;: [&amp;quot;node_modules&amp;quot;]&lt;/code&gt;) and implicit for CRA (accomplished by only &lt;code&gt;&amp;quot;include&amp;quot;: [&amp;quot;src&amp;quot;]&lt;/code&gt;). This does not impact your ability to import and use packages (e.g. &lt;code&gt;import React from &amp;quot;react&amp;quot;&lt;/code&gt; still works), but it does stop VS Code to not attempt to index and provide intellisense on your &lt;code&gt;node_modules&lt;/code&gt; folder.&lt;/p&gt;
&lt;p&gt;So, now you&#39;ve saved some time (and frustration) by binning relative imports, perhaps it&#39;s time to consider &lt;a href=&quot;https://code.visualstudio.com/docs/nodejs/working-with-javascript#_type-checking-javascript&quot;&gt;type checking your JavaScript&lt;/a&gt;? If I&#39;ve wasted minutes on relative imports, then I think I&#39;m into hours (or days!) lost that &lt;code&gt;&amp;quot;checkJs&amp;quot;: true&lt;/code&gt; could have saved me.&lt;/p&gt;
</description>
      <pubDate>Sat, 25 Apr 2020 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2020/04/absolute-imports-with-react/</guid>
    </item>
    <item>
      <title>Getting a list of all performance counters ingested to Log Analytics</title>
      <link>https://tjaddison.com/blog/2020/03/getting-a-list-of-all-performance-counters-ingested-to-log-analytics/</link>
      <description>&lt;p&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-monitor/log-query/log-query-overview&quot;&gt;Log Analytics&lt;/a&gt; is a great product - easy to get data ingested, and easy to query it. It&#39;s also pretty easy to run up a sizeable bill (with the cost being based on GB ingested). I&#39;ve found it helpful to regularly review what is being ingested to a workspace, which I typically do with the following query:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Usage
| where TimeGenerated &amp;gt; ago(30d)
| where IsBillable == true
| summarize TotalVolumeGB = sum(Quantity) / 1024 by Solution, DataType
| order by TotalVolumeGB desc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&#39;ll get to know which solutions are data heavy pretty fast (I&#39;m looking at you &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-monitor/insights/wire-data&quot;&gt;Wire Data&lt;/a&gt; and &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-monitor/insights/dns-analytics&quot;&gt;DNS Analytics&lt;/a&gt;). Something I&#39;ve recently spent some time on is performance counters - after the &lt;code&gt;Perf&lt;/code&gt; data type crept to the top of the list, so I drilled into that with the &lt;code&gt;_BilledSize&lt;/code&gt; property:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Perf
| where TimeGenerated &amp;gt; ago(30d)
| summarize TotalVolumeGB = sum(_BilledSize) / pow(1024, 3) by ObjectName, CounterName // Computer
| order by TotalVolumeGB desc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A recent review showed a fair amount of performance counter overlap between workspaces - my suspicion was that we were ingesting the same counters in multiple workspaces. The following script dumps all counters ingested to any workspace, along with their target instance and frequency. A reliable way to drive up your ingestion costs are the &lt;code&gt;Process/% Processor Time&lt;/code&gt; counter ingested for every instance (&lt;code&gt;*&lt;/code&gt;) at a frequency of 10 seconds.&lt;/p&gt;
&lt;p&gt;We got rid of that one pretty fast.&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;Import-Module&lt;/span&gt; Az&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Connect-AzAccount&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$allPerfCounters&lt;/span&gt; = @&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$subscriptions&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-AzSubscription&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$subscription&lt;/span&gt; in &lt;span class=&quot;token variable&quot;&gt;$subscriptions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token variable&quot;&gt;$subscription&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Set-AzContext&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token variable&quot;&gt;$workspaces&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-AzOperationalInsightsWorkspace&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$workspace&lt;/span&gt; in &lt;span class=&quot;token variable&quot;&gt;$workspaces&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$perfCounters&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-AzOperationalInsightsDataSource&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Kind WindowsPerformanceCounter &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Workspace &lt;span class=&quot;token variable&quot;&gt;$workspace&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$counter&lt;/span&gt; in &lt;span class=&quot;token variable&quot;&gt;$perfCounters&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token variable&quot;&gt;$allPerfCounters&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;[pscustomobject]&lt;/span&gt;@&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        Subscription  = &lt;span class=&quot;token variable&quot;&gt;$subscription&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;br /&gt;        ResourceGroup = &lt;span class=&quot;token variable&quot;&gt;$counter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ResourceGroupName&lt;br /&gt;        Workspace     = &lt;span class=&quot;token variable&quot;&gt;$counter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;WorkspaceName&lt;br /&gt;        ObjectName    = &lt;span class=&quot;token variable&quot;&gt;$counter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Properties&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ObjectName&lt;br /&gt;        InstanceName  = &lt;span class=&quot;token variable&quot;&gt;$counter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Properties&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;InstanceName&lt;br /&gt;        CounterName   = &lt;span class=&quot;token variable&quot;&gt;$counter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Properties&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CounterName&lt;br /&gt;        Interval      = &lt;span class=&quot;token variable&quot;&gt;$counter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Properties&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;IntervalSeconds&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$allPerfCounters&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Format-Table&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2020/03/getting-a-list-of-all-performance-counters-ingested-to-log-analytics/xMx_nuWUAh-295.avif 295w, https://tjaddison.com/blog/2020/03/getting-a-list-of-all-performance-counters-ingested-to-log-analytics/xMx_nuWUAh-590.avif 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2020/03/getting-a-list-of-all-performance-counters-ingested-to-log-analytics/xMx_nuWUAh-295.webp 295w, https://tjaddison.com/blog/2020/03/getting-a-list-of-all-performance-counters-ingested-to-log-analytics/xMx_nuWUAh-590.webp 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2020/03/getting-a-list-of-all-performance-counters-ingested-to-log-analytics/xMx_nuWUAh-295.jpeg 295w, https://tjaddison.com/blog/2020/03/getting-a-list-of-all-performance-counters-ingested-to-log-analytics/xMx_nuWUAh-590.jpeg 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Example Results&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2020/03/getting-a-list-of-all-performance-counters-ingested-to-log-analytics/xMx_nuWUAh-295.jpeg&quot; width=&quot;590&quot; height=&quot;55&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Along with a bit of Excel and the results from the earlier queries, it was easy to work through the list and cut down on ingestion costs.&lt;/p&gt;
&lt;p&gt;For more information on understanding costs I&#39;ve found the following articles helpful:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-monitor/platform/manage-cost-storage&quot;&gt;Manage usage and costs with Azure Monitor logs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-monitor/platform/usage-estimated-costs&quot;&gt;Manage usage and estimated costs in Azure Monitor&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Tue, 31 Mar 2020 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2020/03/getting-a-list-of-all-performance-counters-ingested-to-log-analytics/</guid>
    </item>
    <item>
      <title>Swapping implementations at compile time with craco and Create React App</title>
      <link>https://tjaddison.com/blog/2020/02/swapping-implementations-at-compile-time-with-craco-and-create-react-app/</link>
      <description>&lt;h2 id=&quot;the-problem&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/02/swapping-implementations-at-compile-time-with-craco-and-create-react-app/#the-problem&quot;&gt;The Problem&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When developing apps I&#39;ll sometimes want to mock a dependency for reasons other than unit testing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Simulating flaky behaviour - error responses, slow calls, etc.&lt;/li&gt;
&lt;li&gt;Rapid prototyping of the API experience (before building the implementation)&lt;/li&gt;
&lt;li&gt;Working offline&lt;/li&gt;
&lt;li&gt;Deploying demo apps with an ephemeral backend (reload to start again!)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;I&#39;m a big fan of &lt;a href=&quot;https://create-react-app.dev/&quot;&gt;create react app (CRA)&lt;/a&gt;, so as much as possible I try to avoid ejecting - as such the rest of this post explores a solution to keep us within CRA.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This typically starts out with something like:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; getAllPokemon&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; getPokemon &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./api-store&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// } from &#39;./memory-store&#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Judicious use of toggling the comments definitely gets the job done, though once you go beyond a small handful of files it becomes error prone (releasing dev code to prod...).&lt;/p&gt;
&lt;p&gt;To solve the issue of the comments being everywhere we can use an &lt;a href=&quot;https://create-react-app.dev/docs/adding-custom-environment-variables/&quot;&gt;environment variable&lt;/a&gt; - though unfortunately we can&#39;t use it to dynamically import. The following code is what we&#39;re trying to do:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// This is not valid code!&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  getAllPokemon&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  getPokemon&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;from process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;REACT_APP_PROD&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string-property property&quot;&gt;&#39;./api-store&#39;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./memory-store&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The solution I settled on uses &lt;a href=&quot;https://github.com/gsoft-inc/craco&quot;&gt;craco&lt;/a&gt; (Create React App Configuration Override) to let us modify the webpack config &lt;em&gt;without&lt;/em&gt; ejecting, and the &lt;a href=&quot;https://webpack.js.org/plugins/normal-module-replacement-plugin/&quot;&gt;webpack normal module replacement plugin&lt;/a&gt;. Once we&#39;re done the code will look like this:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; getAllPokemon&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; getPokemon &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./APP_TARGET-store&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The example app is available on GitHub in the &lt;a href=&quot;https://github.com/aedificatorum/craco-swap-example&quot;&gt;craco swap example repo&lt;/a&gt;. &lt;a href=&quot;https://github.com/aedificatorum/craco-swap-example/commit/7d268c9a45ba24eb9b2c9785c49b8dc026fb909d&quot;&gt;This commit&lt;/a&gt; contains all the changes needed to implement craco.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2020/02/swapping-implementations-at-compile-time-with-craco-and-create-react-app/IXDdu9K7b4-295.avif 295w, https://tjaddison.com/blog/2020/02/swapping-implementations-at-compile-time-with-craco-and-create-react-app/IXDdu9K7b4-590.avif 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2020/02/swapping-implementations-at-compile-time-with-craco-and-create-react-app/IXDdu9K7b4-295.webp 295w, https://tjaddison.com/blog/2020/02/swapping-implementations-at-compile-time-with-craco-and-create-react-app/IXDdu9K7b4-590.webp 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2020/02/swapping-implementations-at-compile-time-with-craco-and-create-react-app/IXDdu9K7b4-295.jpeg 295w, https://tjaddison.com/blog/2020/02/swapping-implementations-at-compile-time-with-craco-and-create-react-app/IXDdu9K7b4-590.jpeg 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Pokebattle&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2020/02/swapping-implementations-at-compile-time-with-craco-and-create-react-app/IXDdu9K7b4-295.jpeg&quot; width=&quot;590&quot; height=&quot;483&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h2 id=&quot;from-comments-to-craco&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/02/swapping-implementations-at-compile-time-with-craco-and-create-react-app/#from-comments-to-craco&quot;&gt;From comments to craco&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;initial-state&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/02/swapping-implementations-at-compile-time-with-craco-and-create-react-app/#initial-state&quot;&gt;Initial State&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The starting state for our migration is our app which contains two backends. The code below shows how our &lt;code&gt;getPokemon&lt;/code&gt; call is implemented in the api and in-memory versions:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// api-store.js&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;getPokemon&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;https://pokeapi.co/api/v2/pokemon/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;name&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; resultJson &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; pokemon &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; resultJson&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; resultJson&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; pokemon&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// memory.js&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;getPokemon&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// pokemon is a hard-coded array&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; pokemon&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// app.js&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; getAllPokemon&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; getPokemon &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./api-store&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// } from &#39;./memory-store&#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;For this implementation swap to work the method signatures and return types must be the same. One of my favourite mistakes when mocking an SDK is to forget to make my implementations &lt;code&gt;async&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;add-craco&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/02/swapping-implementations-at-compile-time-with-craco-and-create-react-app/#add-craco&quot;&gt;Add craco&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In addition to craco I&#39;m installing &lt;code&gt;cross-env&lt;/code&gt;, which allows an environment variable to be set in a way that works over Mac and Windows.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;yarn&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; craco&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;yarn&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; cross-env&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once &lt;code&gt;craco&lt;/code&gt; has been installed, you need to update your &lt;code&gt;package.json&lt;/code&gt; to use the &lt;code&gt;craco&lt;/code&gt; command instead of &lt;code&gt;react-scripts&lt;/code&gt;. This ensures that any configuration overrides we specify will get picked up.&lt;/p&gt;
&lt;p&gt;We&#39;re also adding a new start method which will set an environment variable. This is how we&#39;ll decide which implementation is built, and I typically leave the default (no variable) to build the production version.&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token property&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;start&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;craco start&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;start:memory&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;cross-env REACT_APP_MEMORY_STORE=1 craco start&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;build&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;craco build&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;test&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;craco test&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;eject&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;react-scripts eject&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point your app should still work - though you need to toggle comments in order to switch implementations. We&#39;ll fix that next.&lt;/p&gt;
&lt;h3 id=&quot;swap-implementations-based-on-an-environment-variable&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/02/swapping-implementations-at-compile-time-with-craco-and-create-react-app/#swap-implementations-based-on-an-environment-variable&quot;&gt;Swap implementations based on an environment variable&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To hook into webpack we&#39;ll create a &lt;code&gt;craco.config.js&lt;/code&gt; file in our project root. We check to see if the environment variable is set, and if so use the &lt;code&gt;memory&lt;/code&gt; implementation, otherwise default to &lt;code&gt;api&lt;/code&gt;. We then add a plugin and tell it that if it gets any requests to import a module with &lt;code&gt;APP_TARGET&lt;/code&gt; in the name (e.g. from an &lt;code&gt;import&lt;/code&gt; statement), to replace &lt;code&gt;APP_TARGET&lt;/code&gt; with either &lt;code&gt;api&lt;/code&gt; or &lt;code&gt;memory&lt;/code&gt;, and import that instead.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; webpack &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;webpack&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; appTarget &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;REACT_APP_MEMORY_STORE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;memory&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;api&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;webpack&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;webpack&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;NormalModuleReplacementPlugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;(.*)APP_TARGET-(&#92;.*)&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            resource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;request &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; resource&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;              &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;APP_TARGET-&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;              &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;appTarget&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We now need to modify our code to request the import as follows:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; getAllPokemon&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; getPokemon &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./APP_TARGET-store&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And with that we&#39;re done - a different experience when we &lt;code&gt;yarn start&lt;/code&gt; vs. &lt;code&gt;yarn start:memory&lt;/code&gt;!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Any changes to &lt;code&gt;craco.config.js&lt;/code&gt; will require you to restart the dev server, as the file is only process at build/compile time.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
      <pubDate>Sat, 29 Feb 2020 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2020/02/swapping-implementations-at-compile-time-with-craco-and-create-react-app/</guid>
    </item>
    <item>
      <title>Interesting Links - February 2020</title>
      <link>https://tjaddison.com/blog/2020/02/interesting-links-february-2020/</link>
      <description>&lt;ul&gt;
&lt;li&gt;A &lt;a href=&quot;https://twitter.com/KooKiz/status/1221819208352354305&quot;&gt;tweet on Criteo&#39;s migration to .Net core&lt;/a&gt; which has a bunch of interesting comments in the thread. The migration is doing good things for Criteo&#39;s web app that currently runs on 5,000 32-logical-core hosts, peaks at 6 million requests per second, and has an median latency of 10ms (-10% in core!).&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://github.com/tc39/proposal-pipeline-operator/wiki&quot;&gt;JavaScript Pipeline Operator (|&amp;gt;) proposal&lt;/a&gt; makes for interesting reading - the competing proposals are in-depth and worth reading. Also great to see some examples of before/after - I prefer proposal 1, but would also be happy if 4 makes it.&lt;/li&gt;
&lt;li&gt;There are times in interactions with the Azure (notably the billing department) that I can&#39;t help but recall this &lt;a href=&quot;https://www.reddit.com/r/ProgrammerHumor/comments/etkmxb/cloud_is_a_costly_rental_of_someone_elses_computer/&quot;&gt;(AWS variant) image&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The Morning Paper with an excellent summary of the &lt;a href=&quot;https://blog.acolyer.org/2020/01/20/stella-coping-with-complexity-2/&quot;&gt;Stella Paper&lt;/a&gt;, which seeks to understand how operators handle and avert catastrophe in the face of operating complex systems. On the back of the summary I read the whole paper, and (maybe because I&#39;m familiar with the complexities of operating an ecommerce app?) I&#39;d recommend the summary over the paper.&lt;/li&gt;
&lt;li&gt;I&#39;ve previously spent time talking vaguely about why beyond a certain point focusing on force-multiplier/leveraged work is key, &lt;a href=&quot;https://codahale.com/work-is-work/&quot;&gt;this excellent write-up&lt;/a&gt; says it far better than I could. If you&#39;ve even a passing interest in organizational design this is highly recommended reading.&lt;/li&gt;
&lt;li&gt;One way to generate a tonne of leverage across an engineering organization is to align multiple groups on a single Technical Strategy. Implementing that may require generating an &lt;a href=&quot;https://blog.thepete.net/blog/2019/12/09/delivering-on-an-architecture-strategy/&quot;&gt;Architecture Strategy&lt;/a&gt;. Once built the strategy is useless if it isn&#39;t shared, which is why I recommend reading the companion post that covers &lt;a href=&quot;https://blog.thepete.net/blog/2020/01/09/creating-and-sharing-strategic-architectural-initiatives/&quot;&gt;how to create and share architecture initiatives&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;We&#39;ve now been using RFCs in engineering for about a year, and as such have taken some time out to reflect on if the process is delivering the value needed. One thing we recently changed was an explicit readme (answering FAQs that we could have anticipated early on). Re-reading &lt;a href=&quot;https://philcalcado.com/2018/11/19/a_structured_rfc_process.html&quot;&gt;this post from Phil Calçado&lt;/a&gt; I can confirm this is extremely helpful advice if you&#39;re considering an RFC process.&lt;/li&gt;
&lt;li&gt;Expectation vs. Reality - Algorithms in the interview vs. Algorithms on the job. Dan Luu &lt;a href=&quot;https://danluu.com/algorithms-interviews/&quot;&gt;telling it like it is&lt;/a&gt;, though it doesn&#39;t change the fact that most hiring loops are still expecting you to farm &lt;a href=&quot;https://leetcode.com/&quot;&gt;leetcode&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;A lot to agree with in this post that argues &lt;a href=&quot;https://medium.com/@paulosman/production-oriented-development-8ae05f8cc7ea&quot;&gt;the only code worth a damn is the code running production&lt;/a&gt;. The only point I&#39;d hedge on would be the Buy vs. Build point - where I&#39;m actually closer to &lt;em&gt;Buy &amp;amp; Integrate&lt;/em&gt; vs. &lt;em&gt;Build &amp;amp; Operate&lt;/em&gt; (even that breaks down once you stop squinting). I&#39;m not sure yet if this is my optimization function being different, location along product-market fit curve, team culture, something else? Anyway - a thought-provoking article!&lt;/li&gt;
&lt;li&gt;When thinking about team&#39;s, do you first stop to ask question 0 - &lt;a href=&quot;http://www.lindbohm.se/2018/is-this-really-a-team&quot;&gt;Is this really a team?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Whatever side of a 1 on 1 you&#39;re sitting on, it&#39;s always helpful to see how others &lt;a href=&quot;https://marcorogers.com/blog/my-approach-to-1-on-1s&quot;&gt;approach their 1 on 1s&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Zero Trust is dangerously close to becoming a marketer&#39;s term first &amp;amp; foremost (maybe it&#39;s already too late?). Alex Weinert has a &lt;a href=&quot;https://techcommunity.microsoft.com/t5/azure-active-directory-identity/zero-hype/ba-p/1061413&quot;&gt;great overview (and a linked talk)&lt;/a&gt; that spells out what Zero Trust might look like, and if you&#39;re a Microsoft shop there are some explicit next-step videos you can consume (this is Zero Trust, not Zero Marketing). The original &lt;a href=&quot;https://cloud.google.com/beyondcorp/&quot;&gt;Beyond Corp&lt;/a&gt; resources from Google are worth reading if you want more.&lt;/li&gt;
&lt;li&gt;I&#39;m enjoying &lt;a href=&quot;https://www.amazon.com/dp/B06XPJML5D&quot;&gt;Designing Data Intensive Applications&lt;/a&gt; - the easiest way to know if this is for you is to check out this &lt;a href=&quot;https://henrikwarne.com/2019/07/27/book-review-designing-data-intensive-applications/&quot;&gt;summary post&lt;/a&gt;, and if you&#39;re curious you&#39;ll almost certainly enjoy the book. The &lt;a href=&quot;https://github.com/ept/ddia-references&quot;&gt;footnotes on GitHub&lt;/a&gt; are also worth browsing.&lt;/li&gt;
&lt;li&gt;Will Larson&#39;s post on &lt;a href=&quot;https://lethain.com/first-ninety-days-cto-vpe/&quot;&gt;the first 90 days as VPE/CTO&lt;/a&gt; is worth reading for the same reason I encourage everyone to read books like &lt;a href=&quot;https://www.amazon.com/dp/B06XP3GJ7F/&quot;&gt;The Manager&#39;s Path&lt;/a&gt; - you&#39;ll do whatever it is you do far better when you understand the demands of your role, your peer&#39;s roles, and your manager&#39;s role. And if you&#39;re a budding VPE/CTO, there aren&#39;t many articles out there written for you.&lt;/li&gt;
&lt;li&gt;Also from Will Larson, &lt;a href=&quot;https://lethain.com/eng-brand/&quot;&gt;Engineering Brand&lt;/a&gt; explores the challenges of building your company&#39;s engineering brand.&lt;/li&gt;
&lt;li&gt;Although dated from late 2018, this post on &lt;a href=&quot;https://blog.isquaredsoftware.com/2018/11/react-redux-history-implementation/&quot;&gt;the history of React-Redux&lt;/a&gt; is both interesting, educational, and answers a lot of questions I had about &#39;why are things this way&#39; when working with Redux.&lt;/li&gt;
&lt;li&gt;Finally, a pretty dark framing of the future of the internet, is &lt;a href=&quot;https://www.ribbonfarm.com/2020/01/16/the-internet-of-beefs/&quot;&gt;Internet of Beefs&lt;/a&gt;, a piece that argues that the current (and future?) state of the internet is a disinformation warzone (that&#39;s my paraphrasing, and doesn&#39;t do the article justice). I couldn&#39;t help but be reminded of the internet as imagined in &lt;a href=&quot;https://www.amazon.com/dp/B071X3ZWDN/&quot;&gt;Neal Stephenson&#39;s Dodge&lt;/a&gt;. Maybe we&#39;re already there?&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Fri, 07 Feb 2020 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2020/02/interesting-links-february-2020/</guid>
    </item>
    <item>
      <title>Remark Slides Starter</title>
      <link>https://tjaddison.com/blog/2020/01/remark-slide-starter/</link>
      <description>&lt;p&gt;After spending so long authoring technical blog posts/documentation in &lt;a href=&quot;https://www.markdownguide.org/&quot;&gt;Markdown&lt;/a&gt;, it&#39;s a jarring transition to get dropped back into PowerPoint or Keynote and produce a useful technical presentation...I found myself pasting a lot of &lt;em&gt;screenshots&lt;/em&gt; of code, or fighting with formatting. The good news is there are plenty of libraries out there which allow you to author in Markdown.&lt;/p&gt;
&lt;p&gt;I start all my technical presentations with the &lt;a href=&quot;https://github.com/aedificatorum/remark-slide-starter&quot;&gt;remark-slides-starter&lt;/a&gt;, that is built on &lt;a href=&quot;https://remarkjs.com/&quot;&gt;Remark&lt;/a&gt;, &lt;a href=&quot;https://github.com/astefanutti/decktape&quot;&gt;DeckTape&lt;/a&gt;, and &lt;a href=&quot;https://github.com/tapio/live-server&quot;&gt;Live Server&lt;/a&gt;. If you want to get started right away:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; clone https://github.com/aedificatorum/remark-slide-starter.git new-slides&lt;br /&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; new-slides&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; run present&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;You&#39;ll need nodejs installed for this to work. I recommend VS Code as the editor for your Markdown slides.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Otherwise read on for some details of the changes I made, and why I think this is a great default for your presentations.&lt;/p&gt;
&lt;h2 id=&quot;author-remark&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/01/remark-slide-starter/#author-remark&quot;&gt;Author - Remark&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://remarkjs.com/&quot;&gt;Remark&lt;/a&gt; is an in-browser slideshow tool that is driven by Markdown, and supports a couple of enhancements to Markdown that fill in a few gaps that are needed for presentations (speaker notes, slide build, formatting and layout). Crucially for me, it also has great syntax highlighting out of the box.&lt;/p&gt;
&lt;p&gt;The default remark setup has you paste the contents of your slides into a &lt;code&gt;textarea&lt;/code&gt; - this works but it means you&#39;ll get no syntax highlighting, and large presentations can get pretty unwieldy.&lt;/p&gt;
&lt;p&gt;To combat this I split my slides out into one (or more) Markdown files, and load them in with JavaScript. This has the added benefit of allowing slide re-use (import the fragment to multiple presentations):&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- index.html --&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;textarea&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;source&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;textarea&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://remarkjs.com/downloads/remark-latest.min.js&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/slides.md&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;source&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;innerHTML &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;slideshow &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; remark&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As a result of this split you can use VS Code&#39;s preview functionality as you work on the slides:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2020/01/remark-slide-starter/28HHOi6mjM-295.avif 295w, https://tjaddison.com/blog/2020/01/remark-slide-starter/28HHOi6mjM-590.avif 590w, https://tjaddison.com/blog/2020/01/remark-slide-starter/28HHOi6mjM-885.avif 885w, https://tjaddison.com/blog/2020/01/remark-slide-starter/28HHOi6mjM-1180.avif 1180w, https://tjaddison.com/blog/2020/01/remark-slide-starter/28HHOi6mjM-1475.avif 1475w, https://tjaddison.com/blog/2020/01/remark-slide-starter/28HHOi6mjM-1770.avif 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2020/01/remark-slide-starter/28HHOi6mjM-295.webp 295w, https://tjaddison.com/blog/2020/01/remark-slide-starter/28HHOi6mjM-590.webp 590w, https://tjaddison.com/blog/2020/01/remark-slide-starter/28HHOi6mjM-885.webp 885w, https://tjaddison.com/blog/2020/01/remark-slide-starter/28HHOi6mjM-1180.webp 1180w, https://tjaddison.com/blog/2020/01/remark-slide-starter/28HHOi6mjM-1475.webp 1475w, https://tjaddison.com/blog/2020/01/remark-slide-starter/28HHOi6mjM-1770.webp 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2020/01/remark-slide-starter/28HHOi6mjM-295.jpeg 295w, https://tjaddison.com/blog/2020/01/remark-slide-starter/28HHOi6mjM-590.jpeg 590w, https://tjaddison.com/blog/2020/01/remark-slide-starter/28HHOi6mjM-885.jpeg 885w, https://tjaddison.com/blog/2020/01/remark-slide-starter/28HHOi6mjM-1180.jpeg 1180w, https://tjaddison.com/blog/2020/01/remark-slide-starter/28HHOi6mjM-1475.jpeg 1475w, https://tjaddison.com/blog/2020/01/remark-slide-starter/28HHOi6mjM-1770.jpeg 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;VS Code Markdown Preview&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2020/01/remark-slide-starter/28HHOi6mjM-295.jpeg&quot; width=&quot;1770&quot; height=&quot;774&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;I&#39;d encourage you to check out the &lt;a href=&quot;https://remarkjs.com/&quot;&gt;Remark Slideshow&lt;/a&gt; as well as the &lt;a href=&quot;https://github.com/gnab/remark/wiki&quot;&gt;Remark Wiki&lt;/a&gt; to get an overview of all the functionality provided.&lt;/p&gt;
&lt;p&gt;All styles and fonts overrides are contained in the &lt;code&gt;styles.css&lt;/code&gt; file.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that there is an incredibly popular library called remark (which lives in the GitHub org remarkJS) - it has &lt;em&gt;nothing&lt;/em&gt; to do with the remark library I&#39;m discussing in this post. The fact that the Remark slide library owns the remarkjs.com domain name only adds to the confusion!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;export-decktape&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/01/remark-slide-starter/#export-decktape&quot;&gt;Export - DeckTape&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/astefanutti/decktape&quot;&gt;DeckTape&lt;/a&gt; provides PDF export functionality. In order to use you&#39;ll need to be serving your presentation, and then run the export command. DeckTape works by spinning up a headless Chrome instance and &#39;printing&#39; the slides. The results are great, though it can take a little while if you have a very long presentation (50+ slides).&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# In one terminal&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; run present&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# In another terminal&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; run &lt;span class=&quot;token builtin class-name&quot;&gt;export&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default the slides will export to a file named &lt;code&gt;slides.pdf&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;present-live-server&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/01/remark-slide-starter/#present-live-server&quot;&gt;Present - Live Server&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The starter uses &lt;a href=&quot;https://github.com/tapio/live-server&quot;&gt;Live Server&lt;/a&gt; to run the presentation. This will launch a web server that serves the contents of the &lt;code&gt;public&lt;/code&gt; folder, and will be available by default on port 8080. Browse to &lt;a href=&quot;http://localhost:8080/index.html&quot;&gt;http://localhost:8080/index.html&lt;/a&gt; to see your presentation.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you need to customise the port, edit the &lt;code&gt;package.json&lt;/code&gt; file&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The great (greatest?) thing about Live Server is support for hot reload - as you edit any file (CSS, Markdown) the server will reload the file and you&#39;ll see your changes right away.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Live Server is super-useful as a standalone tool - if you want to quickly serve any file or folder you can run &lt;code&gt;npx live-server file-or-folder&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://remarkjs.com/&quot;&gt;Remark&lt;/a&gt; has a few tricks up it&#39;s sleeve for presentation mode. Try pressing &lt;code&gt;P&lt;/code&gt; to enable presenter mode, giving you access to speaker notes, and a view of the next slide. If you press &lt;code&gt;C&lt;/code&gt; you&#39;ll launch a second window which mirrors the current slide, and allows you to effectively present on a second screen.&lt;/p&gt;
&lt;h2 id=&quot;i-need-more&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2020/01/remark-slide-starter/#i-need-more&quot;&gt;I need more!&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If &lt;a href=&quot;https://remarkjs.com/&quot;&gt;Remark&lt;/a&gt; isn&#39;t enough for you, then there are quite a few options of Markdown-driven-slideshows out there. A good starting point is the list of &lt;a href=&quot;https://github.com/astefanutti/decktape#examples&quot;&gt;DeckTape examples&lt;/a&gt; - it lists a few frameworks and links to live demos in addition to the exported PDF.&lt;/p&gt;
&lt;p&gt;I&#39;m really interested in &lt;a href=&quot;https://github.com/jxnblk/mdx-deck&quot;&gt;MDX Deck&lt;/a&gt; as it allows for React components to be embedded in the presentation (via &lt;a href=&quot;https://mdxjs.com/&quot;&gt;MDX&lt;/a&gt; - Markdown with JSX).&lt;/p&gt;
</description>
      <pubDate>Sun, 26 Jan 2020 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2020/01/remark-slide-starter/</guid>
    </item>
    <item>
      <title>Adding a new field to a Firestore collection</title>
      <link>https://tjaddison.com/blog/2019/12/adding-a-new-field-to-a-firestore-collection/</link>
      <description>&lt;p&gt;In my app I tried to work exclusively with dates stored in UTC, though found some combination of the Firestore SDK, the browser, and my (lack of) JavaScript skills - round tripping dates was &lt;em&gt;really hard&lt;/em&gt;. Saving a date at what I thought was midnight then no longer came back in a query that I, again, &lt;em&gt;thought&lt;/em&gt; was from midnight. Date objects are passed on save/query, Firestore actually saves them as a &lt;a href=&quot;https://firebase.google.com/docs/reference/android/com/google/firebase/Timestamp&quot;&gt;Timestamp&lt;/a&gt;, and clear documentation on what should be a pretty common use case for dates - querying on a date range, leads to &lt;a href=&quot;https://stackoverflow.com/questions/47000854/firestore-query-by-date-range&quot;&gt;plenty&lt;/a&gt; &lt;a href=&quot;https://groups.google.com/forum/#!topic/firebase-talk/tOFDwI1a54k&quot;&gt;of&lt;/a&gt; &lt;a href=&quot;https://stackoverflow.com/questions/50705116/range-querying-timestamps-in-firestore-android&quot;&gt;questions&lt;/a&gt;. I decided to reclaim what was left of my sanity and store &lt;a href=&quot;https://en.wikipedia.org/wiki/Unix_time&quot;&gt;unix time&lt;/a&gt; instead. I still need to handle local to UTC on the client, though Firestore is no longer doing anything more than storing a number.&lt;/p&gt;
&lt;p&gt;After updating the app to handle a new field (&lt;code&gt;dateUnix&lt;/code&gt;) going forwards, a backfill of old data was required. The example code below takes every item and stores the converted &lt;code&gt;date&lt;/code&gt; value in a new field &lt;code&gt;dateUnix&lt;/code&gt;. I was surprised to discover there was no way to query by absence of a field (e.g. &lt;code&gt;.where(&#39;date&#39;,&#39;===&#39;, undefined)&lt;/code&gt;), and so instead you&#39;ll need to loop through every item and update if necessary. The code below is designed to run in the browser and assumes you have your Firebase configuration in a file called &lt;code&gt;firebase.js&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;firebase/firestore&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; firebase &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;./firebase&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; db &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; firebase&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;firestore&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; itemsCollection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;collection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;items&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;bulkUpdate&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; limit &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; allItemsResult &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; itemsCollection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;limit&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; read &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; allItemsResult&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;docs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;read &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; batch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;batch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; updated &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    allItemsResult&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;docs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;queryResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; doc &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; queryResult&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;doc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dateUnix&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        updated&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        batch&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;queryResult&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ref&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token comment&quot;&gt;// getTime() returns milliseconds&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token comment&quot;&gt;// We convert to seconds and remove any fractional part&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token literal-property property&quot;&gt;dateUnix&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;doc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toDate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; batch&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;commit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Updated &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;updated&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; of &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;read&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; items!&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; lastVisible &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; allItemsResult&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;docs&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;read &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    allItemsResult &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; itemsCollection&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startAfter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lastVisible&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;limit&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    read &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; allItemsResult&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;docs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some things to note about the script:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We get 50 items at a time
&lt;ul&gt;
&lt;li&gt;We could get everything if the collection is smaller, though then the example wouldn&#39;t feature pagination&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;We work in batches of 50 until there are no items left
&lt;ul&gt;
&lt;li&gt;Working a record at a time does work, though batching speeds things up&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;If the document doesn&#39;t have a dateUnix property, we add it&lt;/li&gt;
&lt;li&gt;We create the new field by using existing data on the document&lt;/li&gt;
&lt;li&gt;We use a transaction to&lt;/li&gt;
&lt;li&gt;When we get the next set of 50 items, we use the last item we saw to control where to start the next page at
&lt;ul&gt;
&lt;li&gt;See &lt;a href=&quot;https://firebase.google.com/docs/firestore/query-data/query-cursors&quot;&gt;Pagination in the Firestore docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Progress is reported to the console...definitely an MVP implementation!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So far I&#39;ve only had to do this on collections with a few hundred documents and it finished in less than a second. I also had the luxury of knowing the collection wasn&#39;t sustaining any concurrent modifications, so I have no idea how this performs on a busy collection.&lt;/p&gt;
&lt;p&gt;One thing I&#39;ve started to do is add an &lt;code&gt;insertedDate&lt;/code&gt; value (using the &lt;a href=&quot;https://firebase.google.com/docs/firestore/manage-data/add-data#server_timestamp&quot;&gt;serverTimestamp&lt;/a&gt;) to all documents, which I could have used to order the collection, and only process rows that weren&#39;t inserting &lt;code&gt;dateUnix&lt;/code&gt; (as I updated the app to load that for all new rows). The other advantage of an &lt;code&gt;insertedDate&lt;/code&gt; is that it allows you to keep track of progress or even partition the work (if you&#39;ve got a very large collection you need to update).&lt;/p&gt;
</description>
      <pubDate>Tue, 31 Dec 2019 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2019/12/adding-a-new-field-to-a-firestore-collection/</guid>
    </item>
    <item>
      <title>More Interesting Links - December 2019</title>
      <link>https://tjaddison.com/blog/2019/12/more-interesting-links-december-2019/</link>
      <description>&lt;ul&gt;
&lt;li&gt;Why invest in creating a new systems programming language? According to Microsoft, the answers are security, and legacy:
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.slideshare.net/KTNUK/digital-security-by-design-security-and-legacy-at-microsoft-matthew-parkinson-microsoft&quot;&gt;Digital security by design - security and legacy at Microsoft&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Based on the most recent &lt;a href=&quot;https://www.brentozar.com/archive/2019/12/sql-constantcare-population-report-fall-2019/&quot;&gt;SQL ConstantCare stats&lt;/a&gt;, what versions of SQL are in the wild? As of December 2019, not a whole lot of 2019, and even 2017 is in a minority (15%). Big boxes (&amp;gt;33 cores) are also notably absent (5%). As someone used to working with 2017 (soon 2019) on some &lt;em&gt;really&lt;/em&gt; big boxes, I wonder where my big-box brethren are.&lt;/li&gt;
&lt;li&gt;Liz Fong-Jones with an impassioned plea to &lt;a href=&quot;https://increment.com/teams/code-less-engineer-more/&quot;&gt;write less code&lt;/a&gt;, and instead see if you can first start by composing, or &lt;em&gt;engineering&lt;/em&gt; a solution.&lt;/li&gt;
&lt;li&gt;Taking composition to an extreme, this thought-provoking write-up of Déjà Vu covers academic work that attempts to make building web apps an exercise in selecting the right legos:
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.acolyer.org/2019/12/04/declarative-assembly-of-web-applications-from-pre-defined-concepts/&quot;&gt;Declarative assembly of web applications from pre-defined concepts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://charticulator.com/&quot;&gt;Charticulator&lt;/a&gt; is an &lt;a href=&quot;https://github.com/Microsoft/charticulator&quot;&gt;open source&lt;/a&gt; visualization generator - the learning curve is a little steep, but the gallery ably demonstrates the breadth of visualizations possible.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://progression.monzo.com/&quot;&gt;Monzo&#39;s progression framework&lt;/a&gt; is an excellent engineering ladder to draw inspiration from (plus a bunch of other roles). Backed by a GatsbyJS app over markdown files if you want to &lt;a href=&quot;https://github.com/monzo/progression-framework&quot;&gt;make it your own&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://miro.com/app/board/o9J_kvmVxh8=/&quot;&gt;This board&lt;/a&gt; contains a bunch of items that either increased momentum, or failed but taught the team something new. Love the framing, and think there are a lot of really interesting ideas (on both sides!). The ideas were sourced from a workshop involving product managers from various organizations.&lt;/li&gt;
&lt;li&gt;State of the art privacy protection in web browsers is getting pretty sophisticated. This article on &lt;a href=&quot;https://brave.com/brave-fingerprinting-and-privacy-budgets/&quot;&gt;Fingerprinting and privacy budgets&lt;/a&gt; from Brave has me in two minds - I care deeply about privacy, but as an operator occasionally tasked with separating bots from real users, I can imagine some of these techniques making that task a little (lot) harder.&lt;/li&gt;
&lt;li&gt;If you like reading SaaS S-1 breakdowns (who doesn&#39;t?), you should follow Alex Clayton&#39;s blog:
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/@alexfclayton&quot;&gt;Alex Clayton on Medium&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Thu, 19 Dec 2019 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2019/12/more-interesting-links-december-2019/</guid>
    </item>
    <item>
      <title>Interesting Links - December 2019</title>
      <link>https://tjaddison.com/blog/2019/12/interesting-links-december-2019/</link>
      <description>&lt;ul&gt;
&lt;li&gt;This whole paper is galaxy-brain - from the fact that Facebook built a load balancer that leveraged users connectedness to the graph that shows 500 million database queries per second. Full of interesting ideas, and mind-boggling numbers - &lt;a href=&quot;https://research.fb.com/publications/taiji-managing-global-user-traffic-for-large-scale-internet-services-at-the-edge/&quot;&gt;Taiji: Managing Global User Traffic for Large-Scale Internet Services at the Edge&lt;/a&gt; is an excellent read.&lt;/li&gt;
&lt;li&gt;The state of &lt;a href=&quot;https://devblogs.microsoft.com/aspnet/improvements-in-net-core-3-0-for-troubleshooting-and-monitoring-distributed-apps/&quot;&gt;distributed tracing in .NET Core 3.0&lt;/a&gt; is looking pretty good, and the move to support the &lt;a href=&quot;https://www.w3.org/TR/trace-context/&quot;&gt;W3C Trace Context spec&lt;/a&gt; and &lt;a href=&quot;https://opentelemetry.io/&quot;&gt;OpenTelemetry&lt;/a&gt; SDKs makes it easier than ever to instrument a distributed app.&lt;/li&gt;
&lt;li&gt;Dating from last May, the paper on &lt;a href=&quot;https://blog.acolyer.org/2018/05/01/azure-accelerated-networking-smartnics-in-the-public-cloud/&quot;&gt;Accelerated Networking in Azure&lt;/a&gt; is interesting both as a reminder of how hard the hosting providers are working to squeeze every last drop of performance out of their compute, and as another example of how reducing team handoffs (in this case hardware to software) leads for better delivery&lt;/li&gt;
&lt;li&gt;GatsbyJS now has support for hot schema rebuilds - I&#39;ve been caught out by not restarting &lt;code&gt;gatsby develop&lt;/code&gt; when updating the schema, so this is a welcome addition. No announcement post yet, but you can see the detail in &lt;a href=&quot;https://github.com/gatsbyjs/gatsby/pull/19092&quot;&gt;this pull request&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;I love Netlify - never has a platform done exactly as it&#39;s promised quite so easily. Turns out there is a whole lot more to Netlify than just hosting though, and the &lt;a href=&quot;https://www.netlify.com/blog/2019/10/07/complete-intro-to-netlify-in-3.5-hours/&quot;&gt;complete intro to Netlify&lt;/a&gt; has an excellent set of notes that enumerate the rather impressive set of features.&lt;/li&gt;
&lt;li&gt;Now I understand &lt;a href=&quot;https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b&quot;&gt;what npx does&lt;/a&gt;!&lt;/li&gt;
&lt;li&gt;If you&#39;re interested in online experimentation then &lt;a href=&quot;https://blog.acolyer.org/2019/11/22/planalyzer/&quot;&gt;the morning paper&#39;s coverage of the PlanAlyzer paper&lt;/a&gt; is a great place to start, and I found myself following every link. The &lt;a href=&quot;http://facebook.github.io/planout/&quot;&gt;PlanOut&lt;/a&gt; documentation is fantastic, as is the paper that introduces PlanOut:
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/pdf/1409.3174v1.pdf&quot;&gt;Designing and deploying online field experiments&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Another Microcode update that&#39;s coming along to indiscriminately cause performance issues. To put this most recent change into context, an excellent paper that covers how syscalls have only gotten worse over time (and you&#39;ll note a few significant regressions associated with the Spectre update):
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.acolyer.org/2019/11/04/an-analysis-of-performance-evolution-of-linuxs-core-operations/&quot;&gt;An analysis of performance evolution of linux&#39;s core operations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/damageboy/status/1194751035136450560&quot;&gt;Damageboy&#39;s thread&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Choose boring technology is an evergreen idea, and it&#39;s nice to get the occasional story of how choosing not-boring code ended up being a (wait for it) bad idea, and the boring code won out in the end:
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://boringtechnology.club/&quot;&gt;Choose boring technology talk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/mcfunley/status/1194713711337852928&quot;&gt;Dan McKinley&#39;s thread on the python middle-tier at Etsy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Sun, 01 Dec 2019 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2019/12/interesting-links-december-2019/</guid>
    </item>
    <item>
      <title>Link Roundups</title>
      <link>https://tjaddison.com/blog/2019/11/link-roundups/</link>
      <description>&lt;p&gt;I&#39;ve started to publish &lt;a href=&quot;https://tjaddison.com/blog/tags/#Links&quot;&gt;link roundups&lt;/a&gt; to the site. I used to share these in weekly/fortnightly newsletters at work, and as I invariably re-shared most of them outside of work anyway, figured I&#39;d put them on the blog. I used to do these weekly, though think I&#39;ll settled for an irregular cadence that sees them come in once or twice a month.&lt;/p&gt;
&lt;p&gt;The links pages are implemented as markdown pages, and in order to prevent them showing up as blog posts (and in all the blog meta pages like tags, history, etc.) I used the &lt;a href=&quot;https://github.com/elboman/gatsby-remark-source-name&quot;&gt;gatsby-remark-source-name&lt;/a&gt; package. My &lt;code&gt;gatsby-config.js&lt;/code&gt; now contains two markdown imports:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;gatsby-source-filesystem&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;__dirname&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/content/blog&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;blog&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;gatsby-source-filesystem&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;__dirname&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/content/links&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;links&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anywhere I need to refer to either blog posts or links posts I can use the &lt;code&gt;name&lt;/code&gt; attribute to disambiguate the &lt;code&gt;allMarkdownRemark&lt;/code&gt; node - the following is an example from the &lt;code&gt;gatsby-node.js&lt;/code&gt; file that builds the &lt;code&gt;/links&lt;/code&gt; pages:&lt;/p&gt;
&lt;pre class=&quot;language-graphql&quot;&gt;&lt;code class=&quot;language-graphql&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property-query&quot;&gt;allMarkdownRemark&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token attr-name&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;fields&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;sourceName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;links&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token attr-name&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;fields&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;frontmatter___date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DESC&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token attr-name&quot;&gt;limit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token object&quot;&gt;edges&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token object&quot;&gt;node&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token object&quot;&gt;fields&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token property&quot;&gt;slug&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can see all the changes needed to add the new post type in &lt;a href=&quot;https://github.com/taddison/personal-site/pull/7&quot;&gt;this pull request&lt;/a&gt;. There are probably a few rough edges - I haven&#39;t got that many links pages so pagination and navigation may require some fixes.&lt;/p&gt;
&lt;p&gt;Once I&#39;ve got a few more links pages up I&#39;ll see about adding a &#39;recent updates&#39; component to the home page, which would show a feed of posts (links or blog entries). The RSS feed probably needs some attention to, as does the navigation (now there are blogs &lt;em&gt;and&lt;/em&gt; links to worry about!).&lt;/p&gt;
&lt;p&gt;For now I&#39;m just glad to &lt;a href=&quot;https://blog.jonudell.net/2007/04/10/too-busy-to-blog-count-your-keystrokes/&quot;&gt;share the keystrokes&lt;/a&gt; with a wider audience.&lt;/p&gt;
</description>
      <pubDate>Sat, 30 Nov 2019 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2019/11/link-roundups/</guid>
    </item>
    <item>
      <title>Interesting Links - November 2019</title>
      <link>https://tjaddison.com/blog/2019/11/interesting-links-November-2019/</link>
      <description>&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/1709.08546&quot;&gt;Task-Based Effectiveness of Basic Visualizations&lt;/a&gt; is an excellent paper that compares and ranks different visualizations for various tasks based on accuracy, speed, and user preference. Figure 3 is an excellent summary. There is a summary available at &lt;a href=&quot;https://blog.acolyer.org/2019/10/25/task-based-effectiveness-of-basic-visualizations/&quot;&gt;The Morning Paper&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Not only does &lt;a href=&quot;https://www.kalzumeus.com/2019/10/28/tether-and-bitfinex/&quot;&gt;Tether and Bitfinex&lt;/a&gt; make a compelling case for the whole thing being a billion dollar fraud, it also servers as a great introduction to cryptocurrency exchanges.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lethain.com/healthchecks/&quot;&gt;Healthchecks at scale&lt;/a&gt; is a great example of how something trivial in the small gets very complicated, very quickly when the numbers get large.&lt;/li&gt;
&lt;li&gt;Once upon a time I thought building technical leverage in an infrastructure team would be easy. Of course people were going to flock to the new, easier, better way of doing X. Experience has since disabused me of this notion, and the article &lt;a href=&quot;https://www.collaborativefund.com/blog/tech/&quot;&gt;Why New Technology Is A Hard Sell&lt;/a&gt; offers four compelling explanations, highlighting that being technically superior isn&#39;t enough.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://twitter.com/johncutlefish&quot;&gt;John Cutler&lt;/a&gt; is always sharing thought provoking ideas in the product management space. &lt;a href=&quot;https://twitter.com/johncutlefish/status/1190689018792443905&quot;&gt;The shifts from waterfall to product team&lt;/a&gt; is an interesting framework when thinking through what kinds of handoffs exist, and where.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://conf.reactjs.org/&quot;&gt;React Conf 2019&lt;/a&gt; videos are now &lt;a href=&quot;https://www.youtube.com/playlist?list=PLPxbbTqCLbGHPxZpw4xj_Wwg8-fdNxJRh&quot;&gt;available on YouTube&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Although targeted more at library authors, the post &lt;a href=&quot;https://reactjs.org/blog/2019/11/06/building-great-user-experiences-with-concurrent-mode-and-suspense.html&quot;&gt;Building Great User Experiences with Concurrent Mode and Suspense&lt;/a&gt; introduces some big ideas (if you want some more concrete check out the &lt;a href=&quot;https://reactjs.org/docs/concurrent-mode-intro.html&quot;&gt;React Concurrent docs&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;I&#39;ve been iterating on weekly updates for a while now (this link post represents the most recent change - rather than an internal linkdump they&#39;re now public). I always enjoy reading about how &lt;a href=&quot;https://lethain.com/weekly-updates/&quot;&gt;other people approach them&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Typescript 3.7 is here, and it&#39;s a &lt;a href=&quot;https://devblogs.microsoft.com/typescript/announcing-typescript-3-7/&quot;&gt;pretty big release&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;And finally - when free isn&#39;t &lt;em&gt;really&lt;/em&gt; free, the &lt;a href=&quot;https://www.instagram.com/p/B4X6IPRAB4u/&quot;&gt;latest from system32comics on Instagram&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Fri, 08 Nov 2019 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2019/11/interesting-links-November-2019/</guid>
    </item>
    <item>
      <title>Adding cover images to your Gatsby blog</title>
      <link>https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/</link>
      <description>&lt;p&gt;If you decide to use GatsbyJS for your blog I would highly recommend starting with a simple starter and building up. &lt;a href=&quot;https://github.com/gatsbyjs/gatsby-starter-blog&quot;&gt;Gatsby blog starter&lt;/a&gt; introduces a handful of concepts and is substantially easier to understand than some of the more advanced starters. One feature that adds some personality to your site is featured images for your posts - both on the post list and on the post itself. The rest of this article walks through the steps needed to go from the &lt;a href=&quot;https://github.com/gatsbyjs/gatsby-starter-blog&quot;&gt;Gatsby blog starter&lt;/a&gt; to having featured images on the post list and post pages.&lt;/p&gt;
&lt;h2 id=&quot;add-the-metadata-to-a-post&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/#add-the-metadata-to-a-post&quot;&gt;Add the metadata to a post&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The first thing to do is update our GraphQL schema, which we do by adding a new property to the blog post frontmatter. To ensure our featured image is also accessible we&#39;ll specify some alt text. As an example, we&#39;ll add the existing image from the &#39;Hello World&#39; post as a featured image. You can give the property any name you want - I&#39;ve used &lt;code&gt;featuredimage&lt;/code&gt; which contains a &lt;code&gt;src&lt;/code&gt; and an &lt;code&gt;alt&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
title: Hello World
date: &amp;quot;2015-05-01T22:12:03.284Z&amp;quot;
description: &amp;quot;Hello World&amp;quot;
featuredimage:
  src: &amp;quot;./salty_egg.jpg&amp;quot;
  alt: &amp;quot;A salty egg&amp;quot;
---
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;If you are running &lt;code&gt;gatsby develop&lt;/code&gt; you need to restart that task, as schema changes (or more broadly any changes that require re-running &lt;code&gt;gatsby-node.js&lt;/code&gt;) aren&#39;t hot-reloadable.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You can verify this has worked by browsing the GraphQL schema at &lt;code&gt;http://localhost:8000/___graphql&lt;/code&gt; and inspecting the &lt;code&gt;allMarkdownRemark&lt;/code&gt; nodes - the &lt;code&gt;frontmatter&lt;/code&gt; should now have a &lt;code&gt;featuredimage&lt;/code&gt; property. The image below shows that our salty egg post has a featured image, and the other two posts have nothing.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/lmWg3Rc2Mu-295.avif 295w, https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/lmWg3Rc2Mu-590.avif 590w, https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/lmWg3Rc2Mu-885.avif 885w, https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/lmWg3Rc2Mu-1180.avif 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/lmWg3Rc2Mu-295.webp 295w, https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/lmWg3Rc2Mu-590.webp 590w, https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/lmWg3Rc2Mu-885.webp 885w, https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/lmWg3Rc2Mu-1180.webp 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/lmWg3Rc2Mu-295.jpeg 295w, https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/lmWg3Rc2Mu-590.jpeg 590w, https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/lmWg3Rc2Mu-885.jpeg 885w, https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/lmWg3Rc2Mu-1180.jpeg 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;GraphiQL showing featuredimage&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/lmWg3Rc2Mu-295.jpeg&quot; width=&quot;1180&quot; height=&quot;442&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-gatsbyjs-image-pipeline&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/#the-gatsbyjs-image-pipeline&quot;&gt;The GatsbyJS image pipeline&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Because our starter has the &lt;a href=&quot;https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-transformer-sharp&quot;&gt;gatsby-transformer-sharp&lt;/a&gt; and &lt;a href=&quot;https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-plugin-sharp&quot;&gt;gatsby-plugin-sharp&lt;/a&gt; plugins any images that are found during the build process will be replaced by &lt;code&gt;ImageSharp&lt;/code&gt; nodes. The documentation for &lt;a href=&quot;https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-plugin-sharp&quot;&gt;gatsby-plugin-sharp&lt;/a&gt; is worth reviewing, as is the &lt;a href=&quot;https://www.gatsbyjs.org/docs/working-with-images/&quot;&gt;working with images in Gatsby&lt;/a&gt; guide. If you want the quick version, where we referred to &lt;code&gt;./salty_egg.png&lt;/code&gt; in our frontmatter, we&#39;ll get back an object we can use with &lt;a href=&quot;https://www.gatsbyjs.org/docs/gatsby-image&quot;&gt;gatsby-image&lt;/a&gt; - and that means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Optimized and resized images&lt;/li&gt;
&lt;li&gt;Multiple images generated for different breakpoints&lt;/li&gt;
&lt;li&gt;Generates placeholder images (for &lt;a href=&quot;https://using-gatsby-image.gatsbyjs.org/blur-up/&quot;&gt;blur-up&lt;/a&gt;/&lt;a href=&quot;https://using-gatsby-image.gatsbyjs.org/traced-svg/&quot;&gt;traced placeholder&lt;/a&gt; effects)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Anywhere you add images to your Gatsby site you&#39;ll want to ensure it goes through the appropriate processing to generate the assets needed to use the &lt;code&gt;Img&lt;/code&gt; component provided by &lt;a href=&quot;https://www.gatsbyjs.org/docs/gatsby-image&quot;&gt;gatsby-image&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;adding-the-image-to-the-post-list&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/#adding-the-image-to-the-post-list&quot;&gt;Adding the image to the post list&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The post list is rendered from &lt;code&gt;src&#92;pages&#92;index.js&lt;/code&gt;, and to add an image to each post we first need to return the image and its alt tag.&lt;/p&gt;
&lt;p&gt;We&#39;re taking advantage of a &lt;a href=&quot;https://www.gatsbyjs.org/docs/gatsby-image/#image-query-fragments&quot;&gt;query fragment&lt;/a&gt; (you can see the &lt;a href=&quot;https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-transformer-sharp/src/fragments.js&quot;&gt;source code for the fragments on GitHub&lt;/a&gt;), and we&#39;re also going to add a few options to limit the maximum size of images that come back:&lt;/p&gt;
&lt;pre class=&quot;language-graphql&quot;&gt;&lt;code class=&quot;language-graphql&quot;&gt;&lt;span class=&quot;token object&quot;&gt;frontmatter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property-query&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token attr-name&quot;&gt;formatString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;MMMM DD, YYYY&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;title&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;description&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token object&quot;&gt;featuredimage&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token object&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token object&quot;&gt;childImageSharp&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token property-query&quot;&gt;fluid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token attr-name&quot;&gt;maxWidth&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token fragment function&quot;&gt;GatsbyImageSharpFluid&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;alt&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&#39;m using &lt;code&gt;fluid&lt;/code&gt; (which generates images that stretch to fill a fluid container), though we could have also used &lt;code&gt;fixed&lt;/code&gt;, which is for images that have (unsurprisingly) a fixed width and height. The &lt;a href=&quot;https://www.gatsbyjs.org/docs/gatsby-image&quot;&gt;gatsby-image&lt;/a&gt; docs are a great place to start if you want to dig into the details.&lt;/p&gt;
&lt;p&gt;To get the image on our page we now need to update the markup. First up we need to import &lt;code&gt;gatsby-image&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Img &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;gatsby-image&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To add the image to each post we first extract the &lt;code&gt;featuredimage&lt;/code&gt; from our node:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;posts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; node &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; title &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; node&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;frontmatter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; node&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fields&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;slug&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; featuredimage &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; node&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;frontmatter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;featuredimage&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And finally render the image if the post has one (in my example I&#39;ve put it between the title and the post - you&#39;ll want to spend a bit of time styling this!):&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  featuredimage &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Img&lt;br /&gt;      fluid&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;featuredimage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;childImageSharp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fluid&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;      alt&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;featuredimage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;alt&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And there we have it, a rather &lt;em&gt;functional&lt;/em&gt; looking image on our post list!&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/OIax4Km6rP-295.avif 295w, https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/OIax4Km6rP-590.avif 590w, https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/OIax4Km6rP-885.avif 885w, https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/OIax4Km6rP-1180.avif 1180w, https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/OIax4Km6rP-1475.avif 1475w, https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/OIax4Km6rP-1770.avif 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/OIax4Km6rP-295.webp 295w, https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/OIax4Km6rP-590.webp 590w, https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/OIax4Km6rP-885.webp 885w, https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/OIax4Km6rP-1180.webp 1180w, https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/OIax4Km6rP-1475.webp 1475w, https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/OIax4Km6rP-1770.webp 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/OIax4Km6rP-295.jpeg 295w, https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/OIax4Km6rP-590.jpeg 590w, https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/OIax4Km6rP-885.jpeg 885w, https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/OIax4Km6rP-1180.jpeg 1180w, https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/OIax4Km6rP-1475.jpeg 1475w, https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/OIax4Km6rP-1770.jpeg 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Post list with image&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/OIax4Km6rP-295.jpeg&quot; width=&quot;1770&quot; height=&quot;1562&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Adding the image to the post page requires repeating the same steps in the &lt;code&gt;&#92;src&#92;templates&#92;blog-post.js&lt;/code&gt; file. Depending on your layout you may want to tweak the settings (max size/height - or maybe you&#39;ll want to override the quality and have higher quality images on your post page?).&lt;/p&gt;
&lt;h2 id=&quot;further-reading&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/#further-reading&quot;&gt;Further reading&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There is a lot of documentation for images in Gatsby, and it isn&#39;t always obvious where to go. I&#39;d suggest working through the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.gatsbyjs.org/docs/working-with-images/&quot;&gt;Working with images in Gatsby article&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.gatsbyjs.org/docs/gatsby-image&quot;&gt;Gatsby-Image documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://using-gatsby-image.gatsbyjs.org/&quot;&gt;Using Gatsby-Image&lt;/a&gt; and &lt;a href=&quot;https://image-processing.gatsbyjs.org/&quot;&gt;Image Processing&lt;/a&gt; for some demos&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And then as a next step maybe you&#39;ll add share-images to your blog post with this great article from &lt;a href=&quot;https://juliangaramendy.dev/&quot;&gt;Julian Garamendy&lt;/a&gt; - &lt;a href=&quot;https://juliangaramendy.dev/custom-open-graph-images-in-gatsby-blog/&quot;&gt;Adding custom Open Graph images to Gatsby starter blog&lt;/a&gt;&lt;/p&gt;
</description>
      <pubDate>Sun, 20 Oct 2019 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2019/10/adding-cover-images-to-your-gatsby-blog/</guid>
    </item>
    <item>
      <title>Migrating from Jekyll to Gatsby</title>
      <link>https://tjaddison.com/blog/2019/09/migrating-from-jekyll-to-gatsby/</link>
      <description>&lt;p&gt;After some tinkering with GatsbyJS, I was pretty excited about moving my blog over from Jekyll - great performance, SEO and accessibility out of the box were one reason, and the fact I know far more React/JS than Ruby was another. This post covers my experiences, as well as a few shortcuts that helped make the transition relatively painless.&lt;/p&gt;
&lt;h2 id=&quot;a-little-history&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/09/migrating-from-jekyll-to-gatsby/#a-little-history&quot;&gt;A little history&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When I first started the blog I was fairly committed to getting up and running as fast as possible - spend time writing content, not endlessly tinkering with a site bereft of any content. In some previous attempts to start blogging, I&#39;d get derailed when I started to &lt;em&gt;write my own blog engine&lt;/em&gt;. As you can imagine those efforts didn&#39;t amount to a great deal.&lt;/p&gt;
&lt;p&gt;Although I was aiming for low-friction I did still impose a few constraints:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I should own my content (eliminated blogging on some platforms)&lt;/li&gt;
&lt;li&gt;The content to be trivially portable (eliminated Wordpress, etc.)&lt;/li&gt;
&lt;li&gt;Content should be easy to write anywhere (this led me to markdown)&lt;/li&gt;
&lt;li&gt;Low/zero initial and ongoing cost&lt;/li&gt;
&lt;li&gt;A template I could live with (desktop + mobile)&lt;/li&gt;
&lt;li&gt;No ads&lt;/li&gt;
&lt;li&gt;Fast&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; on GitHub pages was what I settled for, largely influenced by &lt;a href=&quot;https://haacked.com/archive/2013/12/02/dr-jekyll-and-mr-haack/&quot;&gt;this post from Phil Haack&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Fast forward to today and I&#39;ve been pretty happy with the authoring process. There are a few rough edges, though I blame most of them on my lack of experience with Ruby (rounds down to zero).&lt;/p&gt;
&lt;h2 id=&quot;the-new-stack&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/09/migrating-from-jekyll-to-gatsby/#the-new-stack&quot;&gt;The new stack&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After building a few other projects with &lt;a href=&quot;https://www.gatsbyjs.org/&quot;&gt;GatsbyJS&lt;/a&gt; + &lt;a href=&quot;https://www.netlify.com/&quot;&gt;Netlify&lt;/a&gt; I realized that my current blog workflow needed update. When I ran my blog through &lt;a href=&quot;https://developers.google.com/web/tools/lighthouse/&quot;&gt;Lighthouse&lt;/a&gt; I realized that I&#39;d been neglecting visitor experience because I was happy with my authoring experience.&lt;/p&gt;
&lt;h3 id=&quot;performance&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/09/migrating-from-jekyll-to-gatsby/#performance&quot;&gt;Performance&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Moving the blog posts to the Gatsby pipeline had a significant impact on all the &lt;a href=&quot;https://developers.google.com/web/tools/lighthouse/&quot;&gt;Lighthouse&lt;/a&gt; metrics (higher is better):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;Old&lt;/th&gt;
&lt;th&gt;New&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;87%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accessibility&lt;/td&gt;
&lt;td&gt;74%&lt;/td&gt;
&lt;td&gt;97%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best Practices&lt;/td&gt;
&lt;td&gt;79%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SEO&lt;/td&gt;
&lt;td&gt;82%&lt;/td&gt;
&lt;td&gt;98%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;These scores don&#39;t capture the significant impact that Gatsby&#39;s image toolchain can have. In keeping with the &#39;low friction&#39; approach to authoring I had been...a bit lazy with my images, and rarely bothered to compress them or optimize them for the web. Since moving to Gatsby that all comes for free in addition to a bunch of other optimizations - see &lt;a href=&quot;https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-remark-images&quot;&gt;gatsby-remark-images&lt;/a&gt; for more details.&lt;/p&gt;
&lt;p&gt;To demonstrate the impact this had, take a look at the impact of running images through the Gatsby pipeline had for the &lt;a href=&quot;https://tjaddison.com/about&quot;&gt;about page&lt;/a&gt; (lower is better):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Without&lt;/th&gt;
&lt;th&gt;With&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;First Contentful Paint&lt;/td&gt;
&lt;td&gt;1.1s&lt;/td&gt;
&lt;td&gt;0.8s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to Interactive&lt;/td&gt;
&lt;td&gt;1.8s&lt;/td&gt;
&lt;td&gt;1.6s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max Potential Input Delay&lt;/td&gt;
&lt;td&gt;640ms&lt;/td&gt;
&lt;td&gt;70ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;It&#39;s easy to forget that not everyone is browsing on a 4G connection from a desktop device. Speed matters, and having that baked in for free was great.&lt;/p&gt;
&lt;h3 id=&quot;development&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/09/migrating-from-jekyll-to-gatsby/#development&quot;&gt;Development&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;One of the most compelling reasons to migrate was moving to a language/stack I was comfortable coding in. Gatsby sits on top of React/JavaScript - I&#39;m no expert there but I&#39;m infinitely more qualified in JavaScript than Ruby. There was a bit of a learning curve with GraphQL (which I&#39;d never used before Gatsby), though the documentation of both &lt;a href=&quot;https://graphql.org/&quot;&gt;GraphQL&lt;/a&gt; and Gatsby are excellent, and if you do make the switch I&#39;d encourage you to read and digest them.&lt;/p&gt;
&lt;p&gt;Having a little more experience with frontend development I was comfortable dropping the requirement for an &#39;out of the box template&#39; that I&#39;d be happy with, and I started with nothing more than &lt;code&gt;gatsby new&lt;/code&gt; and built from there. I&#39;ve been very impressed with &lt;a href=&quot;https://tailwindcss.com/&quot;&gt;Tailwind CSS&lt;/a&gt; and enjoyed building a template from the ground up and &lt;em&gt;finishing it&lt;/em&gt; (take that 2013-era Tim!).&lt;/p&gt;
&lt;p&gt;Developing with Gatsby is extremely smooth, and I love the fact I can trivially preview a production version of my site locally - I&#39;ll confess with Jekyll and GitHub pages I may have &#39;committed until it works&#39;. Again - I&#39;m sure some of this is down to my ineptitude with Ruby, though I will say the Gatsby experience is very straightforward - &lt;code&gt;gatsby build &amp;amp;&amp;amp; gatsby serve&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;hosting&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/09/migrating-from-jekyll-to-gatsby/#hosting&quot;&gt;Hosting&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I&#39;ve never had any issues with GitHub pages hosting, though now I&#39;ve seen &lt;a href=&quot;https://www.netlify.com/&quot;&gt;Netlify&lt;/a&gt; I don&#39;t know if I could go back! A couple of fantastic features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Generous free tier (including custom domain + SSL)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.netlify.com/docs/continuous-deployment/&quot;&gt;Deploy previews&lt;/a&gt; (open a pull request and Netlify will build a preview for it)&lt;/li&gt;
&lt;li&gt;One-click setup (I connected to GitHub and Netlify figured out the rest)&lt;/li&gt;
&lt;li&gt;Server-side &lt;a href=&quot;https://www.netlify.com/docs/redirects/&quot;&gt;redirects&lt;/a&gt; (I moved my blog to &lt;code&gt;/blog/&lt;/code&gt; so this was important for me)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If I&#39;m feeling brave I can still push directly to master and know my site will get built and published, so this ticks the low-friction box.&lt;/p&gt;
&lt;h2 id=&quot;migrating-from-jekyll-to-gatsbyjs&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/09/migrating-from-jekyll-to-gatsby/#migrating-from-jekyll-to-gatsbyjs&quot;&gt;Migrating from Jekyll to GatsbyJS&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;building-the-new-site&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/09/migrating-from-jekyll-to-gatsby/#building-the-new-site&quot;&gt;Building the new site&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You can (and I did test) go straight to &lt;a href=&quot;https://github.com/gatsbyjs/gatsby-starter-blog&quot;&gt;gatsby-starter-blog&lt;/a&gt;. There are a bunch of starters available for a blog (Gatsby&#39;s strong ecosystem is another reason I moved to it), though I ended up doing quite a bit of customization. The main changes from the template were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add &lt;a href=&quot;https://tailwindcss.com/&quot;&gt;Tailwind CSS&lt;/a&gt; and &lt;a href=&quot;https://github.com/taddison/personal-site&quot;&gt;Purge CSS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Style the blog posts with Tailwind utility classes&lt;/li&gt;
&lt;li&gt;Add opengraph share images to each post&lt;/li&gt;
&lt;li&gt;Add pagination&lt;/li&gt;
&lt;li&gt;Add an archive page&lt;/li&gt;
&lt;li&gt;Add a tag page&lt;/li&gt;
&lt;li&gt;Add a responsive menu&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can see the source (as well as the painful journey of trial and error in the commit history!) &lt;a href=&quot;https://github.com/taddison/personal-site&quot;&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The following posts were incredibly helpful in getting up and running:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.jerriepelser.com/blog/using-tailwind-with-gatsby/&quot;&gt;Using Tailwind with Gatsby&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ericbusch.net/install-gatsby-with-tailwind-css-and-purgecss-from-scratch&quot;&gt;Install Gatsby with Tailwind CSS and Purge CSS from scratch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ericbusch.net/add-responsive-navigation-menu-to-gatsby-tailwind-css-site&quot;&gt;Add responsive navigation menu to Gatsby Tailwind CSS site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nickymeuleman.netlify.com/blog/gatsby-pagination&quot;&gt;Gatsby Pagination&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://codebushi.com/using-gatsby-image/&quot;&gt;Using Gatsby Image&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/&quot;&gt;Styling markdown posts with Tailwind CSS in GatsbyJS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;migrating-blog-posts&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/09/migrating-from-jekyll-to-gatsby/#migrating-blog-posts&quot;&gt;Migrating blog posts&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The migration was fairly straightforward, and the bulk of the work was moving posts and assets around.&lt;/p&gt;
&lt;p&gt;For some reason (lost to me now - I&#39;m sure it felt like a good idea at the time) I had my posts and assets organized like this:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Post&lt;/span&gt;&lt;br /&gt;/_posts/2019/03/19/post-title.md&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# Post images&lt;/span&gt;&lt;br /&gt;/assets/2019/2019-03-19/picture1.png&lt;br /&gt;/assets/2019/2019-03-19/picture2.png&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That has always been a minor source of friction in my workflow (why weren&#39;t they together!), and the Gatsby way would look more like the following:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Post&lt;/span&gt;&lt;br /&gt;/content/blog/2019/03/post-title/index.md&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# Post images&lt;/span&gt;&lt;br /&gt;/content/blog/2019/03/post-title/picture1.png&lt;br /&gt;/content/blog/2019/03/post-title/picture2.png&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&#39;m sure it would be possible to maintain the current folder structure and stitch the data together in code, though I wanted to move a post and all associated assets around by copying a folder if needed, so I used the following PowerShell to bulk-move everything. Note that I created a new repository, rather than attempting to migrate in-place.&lt;/p&gt;
&lt;p&gt;I migrated a few posts &#39;by hand&#39; before running a script, which is what &lt;code&gt;$maxMigrated&lt;/code&gt; does.&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$sourceLocation&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;C:&#92;src&#92;tjaddison.com&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$targetRoot&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;C:&#92;src&#92;blog&#92;content&#92;blog&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$posts&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-ChildItem&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Path &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$sourceLocation&lt;/span&gt;&#92;_posts&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Recurse &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;File&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$maxMigrated&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;2019-06-30&quot;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$post&lt;/span&gt; in &lt;span class=&quot;token variable&quot;&gt;$posts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$date&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Substring&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;10&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$date&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-ge&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$maxMigrated&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;continue&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$year&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Substring&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;0&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;4&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$month&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Substring&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;5&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$targetContainer&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$targetRoot&lt;/span&gt;&#92;&lt;span class=&quot;token variable&quot;&gt;$year&lt;/span&gt;&#92;&lt;span class=&quot;token variable&quot;&gt;$month&lt;/span&gt;&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$name&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Substring&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;11&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Length &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; 14&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ToLower&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# remove .md&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$blogFolder&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$targetContainer&lt;/span&gt;&#92;&lt;span class=&quot;token variable&quot;&gt;$name&lt;/span&gt;&quot;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;# Move the blog post&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;New-Item&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ItemType Directory &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Path &lt;span class=&quot;token variable&quot;&gt;$blogFolder&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;Move-Item&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Path &lt;span class=&quot;token variable&quot;&gt;$post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;FullName &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Destination &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$blogFolder&lt;/span&gt;&#92;index.md&quot;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;# Find any assets (images, source code, etc.)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$imagePath&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$sourceLocation&lt;/span&gt;&#92;assets&#92;&lt;span class=&quot;token variable&quot;&gt;$year&lt;/span&gt;&#92;&lt;span class=&quot;token variable&quot;&gt;$date&lt;/span&gt;&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Test-Path&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$imagePath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;Get-ChildItem&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Path &lt;span class=&quot;token variable&quot;&gt;$imagePath&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Recurse &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Force &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Copy-Item&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Destination &lt;span class=&quot;token variable&quot;&gt;$blogFolder&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because the images had been moved the references in the markdown posts needed to be updated - in hindsight it is a much saner system to have the images co-located and all references relative. I used the following regex in VSCode to bulk-replace all image references, as well as the shareimage frontmatter.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Images&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;/assets.*/&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;.*&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;))&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;./&lt;span class=&quot;token variable&quot;&gt;$2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# shareimage&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shareimage: http.*/&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;.*&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;shareimage: &lt;span class=&quot;token string&quot;&gt;&quot;./&lt;span class=&quot;token variable&quot;&gt;$2&lt;/span&gt;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;whats-next&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/09/migrating-from-jekyll-to-gatsby/#whats-next&quot;&gt;What&#39;s next?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One outstanding feature I&#39;ve not yet solved is comments. A few times over the years I toyed with adding &lt;a href=&quot;https://disqus.com/&quot;&gt;Disqus&lt;/a&gt; but the level of stuff it injects into the site (ads, trackers, etc.) is frankly obnoxious. I did use &lt;a href=&quot;https://staticman.net/&quot;&gt;Staticman&lt;/a&gt; for a while but found that the spam protection just wasn&#39;t strong enough and I experienced a few issues around availability. Tania Rascia has a great post to &lt;a href=&quot;https://www.taniarascia.com/add-comments-to-static-site/&quot;&gt;get a system up and running with Node.js/Express/PostgreSQL&lt;/a&gt; - this is a little beyond my target complexity, so I&#39;m already toying the idea of rolling my own system atop &lt;a href=&quot;https://firebase.google.com/docs/firestore/&quot;&gt;Cloud Firestore&lt;/a&gt; or &lt;a href=&quot;https://www.netlify.com/docs/form-handling/&quot;&gt;Netlify Forms&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&#39;m still chasing down a few areas where the site is not as accessible as I&#39;d like, and I&#39;ve got some ideas on what an improved home page might look like...but mostly I&#39;m pretty happy with how it looks and excited to focus on writing post again rather than migrating them.&lt;/p&gt;
&lt;p&gt;Let me know if you have any comments or suggestions (by email for now, there is no comment system!)&lt;/p&gt;
</description>
      <pubDate>Mon, 30 Sep 2019 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2019/09/migrating-from-jekyll-to-gatsby/</guid>
    </item>
    <item>
      <title>Styling markdown posts with Tailwind CSS in GatsbyJS</title>
      <link>https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/</link>
      <description>&lt;p&gt;I&#39;m slowly working on migrating the blog from &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt; to &lt;a href=&quot;https://www.gatsbyjs.org/&quot;&gt;GatsbyJS&lt;/a&gt;. I&#39;ve decided to use &lt;a href=&quot;https://tailwindcss.com/&quot;&gt;Tailwind CSS&lt;/a&gt; to style the blog, which means that out of the box (once Tailwind&#39;s pretty &lt;a href=&quot;https://tailwindcss.com/docs/preflight/&quot;&gt;aggressive reset&lt;/a&gt; has been applied) all the markdown posts end up being unstyled. In the below example the only styling on any element comes courtesy of the &lt;a href=&quot;https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-remark-prismjs&quot;&gt;gatsby-remark-prismjs&lt;/a&gt; package, which handles code styling.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/8l1xu-mFTY-295.avif 295w, https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/8l1xu-mFTY-590.avif 590w, https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/8l1xu-mFTY-885.avif 885w, https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/8l1xu-mFTY-1180.avif 1180w, https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/8l1xu-mFTY-1475.avif 1475w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/8l1xu-mFTY-295.webp 295w, https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/8l1xu-mFTY-590.webp 590w, https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/8l1xu-mFTY-885.webp 885w, https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/8l1xu-mFTY-1180.webp 1180w, https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/8l1xu-mFTY-1475.webp 1475w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/8l1xu-mFTY-295.jpeg 295w, https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/8l1xu-mFTY-590.jpeg 590w, https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/8l1xu-mFTY-885.jpeg 885w, https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/8l1xu-mFTY-1180.jpeg 1180w, https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/8l1xu-mFTY-1475.jpeg 1475w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Unstyled Post&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/8l1xu-mFTY-295.jpeg&quot; width=&quot;1475&quot; height=&quot;508&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;After adding some styling to the &lt;code&gt;site.css&lt;/code&gt; file we&#39;re able to get the post looking halfway decent.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/5wNyBaKfGZ-295.avif 295w, https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/5wNyBaKfGZ-590.avif 590w, https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/5wNyBaKfGZ-885.avif 885w, https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/5wNyBaKfGZ-1180.avif 1180w, https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/5wNyBaKfGZ-1475.avif 1475w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/5wNyBaKfGZ-295.webp 295w, https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/5wNyBaKfGZ-590.webp 590w, https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/5wNyBaKfGZ-885.webp 885w, https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/5wNyBaKfGZ-1180.webp 1180w, https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/5wNyBaKfGZ-1475.webp 1475w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/5wNyBaKfGZ-295.jpeg 295w, https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/5wNyBaKfGZ-590.jpeg 590w, https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/5wNyBaKfGZ-885.jpeg 885w, https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/5wNyBaKfGZ-1180.jpeg 1180w, https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/5wNyBaKfGZ-1475.jpeg 1475w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Styled Post&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/5wNyBaKfGZ-295.jpeg&quot; width=&quot;1475&quot; height=&quot;840&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;I wanted to keep the Tailwind reset applied to the non-markdown parts of the site, so the blog template renders each post into a &lt;code&gt;div&lt;/code&gt; with the &lt;code&gt;markdown&lt;/code&gt; class, and the &lt;code&gt;site.css&lt;/code&gt; is updated to include the below definitions. I&#39;m using &lt;a href=&quot;https://www.purgecss.com/&quot;&gt;Purge CSS&lt;/a&gt; to strip unused classes, and because these classes aren&#39;t directly used anywhere (e.g. &lt;code&gt;className=&amp;quot;...&amp;quot;&lt;/code&gt;) we need to tell Purge CSS to ignore them.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Watch out for PurgeCSS and Prism! If you don&#39;t specify &lt;code&gt;prismjs/&lt;/code&gt; as an ignore, your production builds will be unstyled. PurgeCSS only runs for production builds (&lt;code&gt;gatsby build&lt;/code&gt;), so make sure you test the look &amp;amp; feel of your site after doing a production build, or you might be surprised when you deploy...&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* purgecss start ignore */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;/* Markdown Styles */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;/* Global */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;.markdown&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@apply&lt;/span&gt; leading-relaxed text-sm&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@screen&lt;/span&gt; sm&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;.markdown&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@apply&lt;/span&gt; text-base&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@screen&lt;/span&gt; lg&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;.markdown&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@apply&lt;/span&gt; text-lg&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;/* Headers */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;.markdown h1,&lt;br /&gt;.markdown h2&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@apply&lt;/span&gt; text-xl my-6 font-semibold&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;.markdown h3,&lt;br /&gt;.markdown h4,&lt;br /&gt;.markdown h5,&lt;br /&gt;.markdown h6&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@apply&lt;/span&gt; text-lg my-3 font-semibold&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@screen&lt;/span&gt; sm&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;.markdown h1,&lt;br /&gt;  .markdown h2&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@apply&lt;/span&gt; text-2xl&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token selector&quot;&gt;.markdown h3,&lt;br /&gt;  .markdown h4,&lt;br /&gt;  .markdown h5,&lt;br /&gt;  .markdown h6&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@apply&lt;/span&gt; text-xl&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;/* Links */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;.markdown a&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@apply&lt;/span&gt; text-blue-600&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;.markdown a:hover&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@apply&lt;/span&gt; underline&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;/* Paragraph */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;.markdown p&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@apply&lt;/span&gt; mb-4&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;/* Lists */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;.markdown ul,&lt;br /&gt;.markdown ol&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@apply&lt;/span&gt; mb-4 ml-8&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;.markdown li &gt; p,&lt;br /&gt;.markdown li &gt; ul,&lt;br /&gt;.markdown li &gt; ol&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@apply&lt;/span&gt; mb-0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;.markdown ol&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@apply&lt;/span&gt; list-decimal&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;.markdown ul&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@apply&lt;/span&gt; list-disc&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;/* Blockquotes */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;.markdown blockquote&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@apply&lt;/span&gt; p-2 mx-6 bg-gray-100 mb-4 border-l-4 border-gray-400 italic&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;.markdown blockquote &gt; p&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@apply&lt;/span&gt; mb-0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;/* Tables */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;.markdown td,&lt;br /&gt;.markdown th&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@apply&lt;/span&gt; px-2 py-1 border border-gray-400&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;.markdown tr:nth-child(odd)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@apply&lt;/span&gt; bg-gray-100&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;.markdown table&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@apply&lt;/span&gt; mb-6&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;/* Wrap any inline highlights `that are really long`, but don&#39;t modify&lt;br /&gt;   the setting for codeblocks (inside ```), which are rendered in as:&lt;br /&gt;   &amp;lt;pre&gt;&amp;lt;code&gt;...&lt;br /&gt;*/&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;.markdown :not(pre) &gt; code.language-text&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;white-space&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; pre-line&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;/* purgecss end ignore */&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a pretty workable solution for most of my posts, though there are still a few areas I&#39;d like to improve (notably padding for images, and the appearance at a few breakpoints isn&#39;t ideal).&lt;/p&gt;
&lt;h3 id=&quot;further-reading&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/#further-reading&quot;&gt;Further Reading&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The following posts were incredibly helpful in getting Gatsby and Tailwind configured:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ericbusch.net/install-gatsby-with-tailwind-css-and-purgecss-from-scratch&quot;&gt;Install Gatsby with Tailwind CSS and PurgeCSS from Scratch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.jerriepelser.com/blog/using-tailwind-with-gatsby/&quot;&gt;Using Tailwind with Gatsby&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you do decide to take the plunge with Tailwind I strongly suggest you read through &lt;a href=&quot;https://tailwindcss.com/docs/preflight/&quot;&gt;what the preflight does&lt;/a&gt;, as it really does reset things!&lt;/p&gt;
</description>
      <pubDate>Sat, 31 Aug 2019 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2019/08/styling-markdown-tailwind-gatsby/</guid>
    </item>
    <item>
      <title>Allowing only specified users to access Cloud Firestore</title>
      <link>https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/</link>
      <description>&lt;p&gt;I&#39;ve been building a few apps recently that leverage &lt;a href=&quot;https://firebase.google.com/docs/firestore/&quot;&gt;Cloud Firestore&lt;/a&gt; for data storage. These are personal apps and don&#39;t store anything particularly sensitive, though that is no reason to leave them in the default development configuration that let&#39;s anyone read/write everything.&lt;/p&gt;
&lt;p&gt;Although in many projects I&#39;m the only user, there are handful of others where a few people are using the app. A fairly flexible configuration approach that I use as my default is to only allow access if the user is in an &#39;allow list&#39;.&lt;/p&gt;
&lt;p&gt;I&#39;ll show the steps needed to do this below, the pre-requisites are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cloud Firestore enabled for the project&lt;/li&gt;
&lt;li&gt;Authentication configured for the project with at least one user authenticated
&lt;ul&gt;
&lt;li&gt;Every user you want to grant access will need to authenticate with the project as we&#39;re using their firebase User UID, which is unique to each project&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;implementing-an-allow-list-in-firestore&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/#implementing-an-allow-list-in-firestore&quot;&gt;Implementing an allow list in Firestore&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To make this work we&#39;re going to create a security rule which will allow users to read/write any part of the database only if they exist in a specific collection, which we will manually populate.&lt;/p&gt;
&lt;p&gt;To get started you&#39;ll need the User UID of a user who has previously authenticated with the project. In the example below I&#39;m using testuser.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/v8gpk81KdN-295.avif 295w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/v8gpk81KdN-590.avif 590w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/v8gpk81KdN-885.avif 885w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/v8gpk81KdN-1180.avif 1180w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/v8gpk81KdN-1475.avif 1475w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/v8gpk81KdN-1770.avif 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/v8gpk81KdN-295.webp 295w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/v8gpk81KdN-590.webp 590w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/v8gpk81KdN-885.webp 885w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/v8gpk81KdN-1180.webp 1180w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/v8gpk81KdN-1475.webp 1475w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/v8gpk81KdN-1770.webp 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/v8gpk81KdN-295.jpeg 295w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/v8gpk81KdN-590.jpeg 590w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/v8gpk81KdN-885.jpeg 885w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/v8gpk81KdN-1180.jpeg 1180w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/v8gpk81KdN-1475.jpeg 1475w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/v8gpk81KdN-1770.jpeg 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;User list&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/v8gpk81KdN-295.jpeg&quot; width=&quot;1770&quot; height=&quot;634&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Create a new collection called &lt;code&gt;allow-users&lt;/code&gt; and for the first document specify the User UID as the document ID. No need to add any fields (though I&#39;ve found adding a friendly name to remember what the UIDs map to is helpful).&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/TPIkkCUgaU-295.avif 295w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/TPIkkCUgaU-590.avif 590w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/TPIkkCUgaU-885.avif 885w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/TPIkkCUgaU-1180.avif 1180w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/TPIkkCUgaU-1475.avif 1475w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/TPIkkCUgaU-1770.avif 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/TPIkkCUgaU-295.webp 295w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/TPIkkCUgaU-590.webp 590w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/TPIkkCUgaU-885.webp 885w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/TPIkkCUgaU-1180.webp 1180w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/TPIkkCUgaU-1475.webp 1475w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/TPIkkCUgaU-1770.webp 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/TPIkkCUgaU-295.jpeg 295w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/TPIkkCUgaU-590.jpeg 590w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/TPIkkCUgaU-885.jpeg 885w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/TPIkkCUgaU-1180.jpeg 1180w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/TPIkkCUgaU-1475.jpeg 1475w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/TPIkkCUgaU-1770.jpeg 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;ALlow users collection&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/TPIkkCUgaU-295.jpeg&quot; width=&quot;1770&quot; height=&quot;633&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Now configure your Firestore security rules. If you&#39;re using the &lt;a href=&quot;https://www.npmjs.com/package/firebase-tools&quot;&gt;Firebase CLI&lt;/a&gt; you would deploy these using a &lt;code&gt;firebase.rules&lt;/code&gt; file, or you can paste into the console and publish.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// firestore.rules&lt;/span&gt;&lt;br /&gt;rules_version &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;2&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;service cloud&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;firestore &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  match &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;databases&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;database&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;documents &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    match &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;document&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      allow read&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isSignedIn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isAllowedUser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isSignedIn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;uid &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isAllowedUser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;exists&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;databases&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;database&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;documents&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;allow&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;users&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;uid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/iAv5aPeT6R-295.avif 295w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/iAv5aPeT6R-590.avif 590w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/iAv5aPeT6R-885.avif 885w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/iAv5aPeT6R-1180.avif 1180w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/iAv5aPeT6R-1475.avif 1475w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/iAv5aPeT6R-1770.avif 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/iAv5aPeT6R-295.webp 295w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/iAv5aPeT6R-590.webp 590w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/iAv5aPeT6R-885.webp 885w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/iAv5aPeT6R-1180.webp 1180w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/iAv5aPeT6R-1475.webp 1475w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/iAv5aPeT6R-1770.webp 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/iAv5aPeT6R-295.jpeg 295w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/iAv5aPeT6R-590.jpeg 590w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/iAv5aPeT6R-885.jpeg 885w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/iAv5aPeT6R-1180.jpeg 1180w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/iAv5aPeT6R-1475.jpeg 1475w, https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/iAv5aPeT6R-1770.jpeg 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Rules&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/iAv5aPeT6R-295.jpeg&quot; width=&quot;1770&quot; height=&quot;1084&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;From here you can either add additional users to the allow list, or if it&#39;s a personal project you&#39;re done!&lt;/p&gt;
&lt;p&gt;Some things to keep in mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There is no longer any unauthenticated access possible to Firestore - if you need Firestore data to render your app/login this won&#39;t work&lt;/li&gt;
&lt;li&gt;Everyone who can access the app gets full access to everything - read and write&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;references-and-further-reading&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/#references-and-further-reading&quot;&gt;References and further reading&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://firebase.google.com/docs/firestore/&quot;&gt;Cloud Firestore&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/firebase-tools&quot;&gt;Firebase CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://firebase.google.com/docs/firestore/security/get-started&quot;&gt;Cloud Firestore security rules&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Wed, 31 Jul 2019 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2019/07/allowing-only-specified-users-to-access-cloud-firestore/</guid>
    </item>
    <item>
      <title>Create React App, Emotion, and Tailwind CSS starter app</title>
      <link>https://tjaddison.com/blog/2019/06/create-react-app-emotion-and-tailwind-css-starter-app/</link>
      <description>&lt;p&gt;Get up and running fast with &lt;a href=&quot;https://facebook.github.io/create-react-app/&quot;&gt;Create React App&lt;/a&gt; (CRA), &lt;a href=&quot;https://emotion.sh/docs/introduction&quot;&gt;Emotion&lt;/a&gt;, and &lt;a href=&quot;https://tailwindcss.com/&quot;&gt;Tailwind CSS&lt;/a&gt; by cloning the &lt;a href=&quot;https://github.com/aedificatorum/cra-tailwind-emotion-starter.git&quot;&gt;cra-tailwind-emotion-starter&lt;/a&gt; git repo:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; clone https://github.com/aedificatorum/cra-tailwind-emotion-starter.git my-app&lt;br /&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; my-app&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; start&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2019/06/create-react-app-emotion-and-tailwind-css-starter-app/lWHmnSrI9g-295.avif 295w, https://tjaddison.com/blog/2019/06/create-react-app-emotion-and-tailwind-css-starter-app/lWHmnSrI9g-590.avif 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2019/06/create-react-app-emotion-and-tailwind-css-starter-app/lWHmnSrI9g-295.webp 295w, https://tjaddison.com/blog/2019/06/create-react-app-emotion-and-tailwind-css-starter-app/lWHmnSrI9g-590.webp 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2019/06/create-react-app-emotion-and-tailwind-css-starter-app/lWHmnSrI9g-295.jpeg 295w, https://tjaddison.com/blog/2019/06/create-react-app-emotion-and-tailwind-css-starter-app/lWHmnSrI9g-590.jpeg 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;App Splash&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2019/06/create-react-app-emotion-and-tailwind-css-starter-app/lWHmnSrI9g-295.jpeg&quot; width=&quot;590&quot; height=&quot;457&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h2 id=&quot;tailwind-css&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/06/create-react-app-emotion-and-tailwind-css-starter-app/#tailwind-css&quot;&gt;Tailwind CSS&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;tailwind-config&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/06/create-react-app-emotion-and-tailwind-css-starter-app/#tailwind-config&quot;&gt;Tailwind Config&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The only change from the default is to add a custom colour (aptly named &lt;code&gt;customcolor&lt;/code&gt;) to the themes. The tailwind config is used by both the static CSS and the &lt;code&gt;tw&lt;/code&gt; macro.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* src/styles/tailwind.js */&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;theme&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;extend&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token literal-property property&quot;&gt;colors&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token literal-property property&quot;&gt;customcolor&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#fde396&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;variants&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token literal-property property&quot;&gt;plugins&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;static-css&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/06/create-react-app-emotion-and-tailwind-css-starter-app/#static-css&quot;&gt;Static CSS&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;By default only the &lt;a href=&quot;https://tailwindcss.com/docs/adding-base-styles&quot;&gt;base styles&lt;/a&gt; have been added to the static CSS (skipping components and utilities). Anchor has also been updated to be underlined. Note that the &lt;code&gt;tw&lt;/code&gt; macro will inline styles, so there is no need to add the component and utility classes here.&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* src/styles/tailwind.css */&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@tailwind&lt;/span&gt; base&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token selector&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@apply&lt;/span&gt; underline&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PostCSS is used to transform this to &lt;code&gt;tailwind.out.css&lt;/code&gt;, which is imported by &lt;code&gt;index.js&lt;/code&gt;. The output file is in &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;tw-macro&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/06/create-react-app-emotion-and-tailwind-css-starter-app/#tw-macro&quot;&gt;&lt;code&gt;tw&lt;/code&gt; Macro&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;tw&lt;/code&gt; macro allows Tailwind to be used directly by a JSS (CSS in JS) library. In this case we&#39;re using &lt;a href=&quot;https://emotion.sh/docs/introduction&quot;&gt;Emotion&lt;/a&gt; - see below for examples.&lt;/p&gt;
&lt;h2 id=&quot;emotion&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/06/create-react-app-emotion-and-tailwind-css-starter-app/#emotion&quot;&gt;Emotion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;App.js&lt;/code&gt; contains a few different ways you can use Tailwind and Emotion together. The below example is a simplified version that demonstrates the various ways to use Emotion or Emotion + Tailwind, in addition to calling out which imports are required in each scenario.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/* src/App.js */&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; tw &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;tailwind.macro&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;/** @jsx jsx */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; css&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; jsx &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@emotion/core&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; styled &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@emotion/styled&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; Section &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; styled&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;div&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&lt;br /&gt;  &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;tw&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;bg-red-900 min-h-screen flex flex-col items-center justify-center text-xl text-white&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;;&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;App&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Fragment&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Section&lt;br /&gt;        css&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token literal-property property&quot;&gt;backgroundColor&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;black&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;        This requires the pragma &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; jsx &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;Section&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Section&lt;br /&gt;        css&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;css&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&lt;br /&gt;          color: hotpink;&lt;br /&gt;          background-color: white;&lt;br /&gt;        &lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;        This requires the css &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;Section&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Section css&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;tw&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;bg-teal-300&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;This uses the Tailwind &lt;span class=&quot;token function&quot;&gt;macro&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tw&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;Section&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Section css&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;tw&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;bg-customcolor&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;        And &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt; uses the custom color defined &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;pre&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;tailwind&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;js&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;pre&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;Section&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;React&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Fragment&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; App&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For more information see the docs on &lt;a href=&quot;https://emotion.sh/docs/styled&quot;&gt;Emotion styled components&lt;/a&gt;, the &lt;a href=&quot;https://emotion.sh/docs/css-prop&quot;&gt;Emotion css prop&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;credits&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/06/create-react-app-emotion-and-tailwind-css-starter-app/#credits&quot;&gt;Credits&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The following posts were helpful in putting this starter app together:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.nardsparagas.com/cra-and-tailwind/&quot;&gt;React and Tailwind&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wetainment.com/articles/tailwind-css-in-js/&quot;&gt;Tailwind CSS in JS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Sun, 30 Jun 2019 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2019/06/create-react-app-emotion-and-tailwind-css-starter-app/</guid>
    </item>
    <item>
      <title>Keeping Application Insights Costs Under Control</title>
      <link>https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/</link>
      <description>&lt;p&gt;Application Insights (now part of &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-monitor/overview&quot;&gt;Azure Monitor&lt;/a&gt;) uses a &lt;a href=&quot;https://azure.microsoft.com/en-us/pricing/details/monitor/&quot;&gt;pay-per-GB-ingested model&lt;/a&gt;, and charges $2.30 per-GB once you exceed the monthly free limit of 5GB. It may surprise you (it certainly surprised me!) to see that by default an Application Insights resource doesn&#39;t deploy with a daily cap of 0.161GB (5GB/month), but actually deploys with a &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-monitor/app/pricing&quot;&gt;daily cap&lt;/a&gt; of 100GB!&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/9lYFXOM_HT-295.avif 295w, https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/9lYFXOM_HT-590.avif 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/9lYFXOM_HT-295.webp 295w, https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/9lYFXOM_HT-590.webp 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/9lYFXOM_HT-295.jpeg 295w, https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/9lYFXOM_HT-590.jpeg 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Application Insights default cap&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/9lYFXOM_HT-295.jpeg&quot; width=&quot;590&quot; height=&quot;218&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Left unchecked each resource like this could end up costing you a cool &lt;strong&gt;$7,118.50 per month&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In order to vet your estate and bring it under control, the PowerShell script below will check every Application Insights resource you have deployed against a limit you set, and optionally reduce anything exceeding that limit to a more reasonable cap.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/aJE58Wcgjs-295.avif 295w, https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/aJE58Wcgjs-590.avif 590w, https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/aJE58Wcgjs-885.avif 885w, https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/aJE58Wcgjs-1180.avif 1180w, https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/aJE58Wcgjs-1475.avif 1475w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/aJE58Wcgjs-295.webp 295w, https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/aJE58Wcgjs-590.webp 590w, https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/aJE58Wcgjs-885.webp 885w, https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/aJE58Wcgjs-1180.webp 1180w, https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/aJE58Wcgjs-1475.webp 1475w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/aJE58Wcgjs-295.jpeg 295w, https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/aJE58Wcgjs-590.jpeg 590w, https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/aJE58Wcgjs-885.jpeg 885w, https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/aJE58Wcgjs-1180.jpeg 1180w, https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/aJE58Wcgjs-1475.jpeg 1475w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Lower that daily cap&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/aJE58Wcgjs-295.jpeg&quot; width=&quot;1475&quot; height=&quot;109&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;In the above example I ran the script against a newly deployed resource, configured to reduce anything with a cap greater than 10GB down to 1GB.&lt;/p&gt;
&lt;h2 id=&quot;script-details&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/#script-details&quot;&gt;Script Details&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The script has three different configuration options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$CAP_CUTOFF&lt;/code&gt; - the daily GB cap at which to take action - any resources beyond this will be capped to the &lt;code&gt;$CAP_TO&lt;/code&gt; value&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$CAP_TO&lt;/code&gt; - the daily limit to set for anything exceeding the &lt;code&gt;$CAP_CUTOFF&lt;/code&gt; value&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$ignoreResources&lt;/code&gt; - An array of resources to ignore. These resources will appear in the report, but no action will be taken&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;If you don&#39;t want the script to make any changes to your environment append &lt;code&gt;-WhatIf&lt;/code&gt; to the &lt;code&gt;Set-AzApplicationInsightsDailyCap&lt;/code&gt; command, or comment it out entirely&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Once the script executes you&#39;ll be asked to authenticate to Azure, and it will then enumerate all subscriptions and all Application Insights resources in those subscriptions.&lt;/p&gt;
&lt;p&gt;Once complete the script will report a list of all actions (either Ignore, Under Cap, or Reduce To X) for each resource it encountered. The example below shows this running against an environment where there is a single resource ignored, and then others are all under the cap - in this case set to 10GB.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/37V47j6H4T-295.avif 295w, https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/37V47j6H4T-590.avif 590w, https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/37V47j6H4T-885.avif 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/37V47j6H4T-295.webp 295w, https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/37V47j6H4T-590.webp 590w, https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/37V47j6H4T-885.webp 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/37V47j6H4T-295.jpeg 295w, https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/37V47j6H4T-590.jpeg 590w, https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/37V47j6H4T-885.jpeg 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Script results&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/37V47j6H4T-295.jpeg&quot; width=&quot;885&quot; height=&quot;390&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-script&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/#the-script&quot;&gt;The Script&lt;/a&gt;&lt;/h2&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;Import-Module&lt;/span&gt; Az&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Connect-AzAccount&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$CAP_CUTOFF&lt;/span&gt; = 10 &lt;span class=&quot;token comment&quot;&gt;## Free tier = 0.161 - 5GB/month&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$CAP_TO&lt;/span&gt; = 1&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$ignoreResources&lt;/span&gt; = @&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Subscription.Resource Group.Resource&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$subscriptions&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-AzSubscription&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$actions&lt;/span&gt; = @&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$sub&lt;/span&gt; in &lt;span class=&quot;token variable&quot;&gt;$subscriptions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$subscriptionName&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$sub&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$sub&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Set-AzContext&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$appInsightResources&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-AzApplicationInsights&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$aiResources&lt;/span&gt; = @&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$ai&lt;/span&gt; in &lt;span class=&quot;token variable&quot;&gt;$appInsightResources&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$aiResources&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Get-AzApplicationInsights&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ResourceGroupName &lt;span class=&quot;token variable&quot;&gt;$ai&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ResourceGroupName &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Name &lt;span class=&quot;token variable&quot;&gt;$ai&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;IncludeDailyCap&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$ai&lt;/span&gt; in &lt;span class=&quot;token variable&quot;&gt;$aiResources&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$identifier&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token function&quot;&gt;$&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$subscriptionName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;span class=&quot;token function&quot;&gt;$&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$ai&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ResourceGroupName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;.&lt;span class=&quot;token function&quot;&gt;$&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$ai&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$ai&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Cap &lt;span class=&quot;token operator&quot;&gt;-gt&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$CAP_CUTOFF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$ignoreResources&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-contains&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$identifier&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token variable&quot;&gt;$action&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;Ignore&quot;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token variable&quot;&gt;$action&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;Reduce to &lt;span class=&quot;token variable&quot;&gt;$CAP_TO&lt;/span&gt;&quot;&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token function&quot;&gt;Set-AzApplicationInsightsDailyCap&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ResourceGroupName &lt;span class=&quot;token variable&quot;&gt;$ai&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ResourceGroupName &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Name &lt;span class=&quot;token variable&quot;&gt;$ai&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;DailyCapGB &lt;span class=&quot;token variable&quot;&gt;$CAP_TO&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token variable&quot;&gt;$action&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;Below &lt;span class=&quot;token variable&quot;&gt;$CAP_CUTOFF&lt;/span&gt;&quot;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$actions&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;[PSCustomObject]&lt;/span&gt;@&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            Subscription = &lt;span class=&quot;token variable&quot;&gt;$subscriptionName&lt;/span&gt;&lt;br /&gt;            ResourceGroup = &lt;span class=&quot;token variable&quot;&gt;$ai&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ResourceGroupName&lt;br /&gt;            ResourceName = &lt;span class=&quot;token variable&quot;&gt;$ai&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;br /&gt;            Cap = &lt;span class=&quot;token variable&quot;&gt;$ai&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Cap&lt;br /&gt;            MonthlyCost = &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$ai&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Cap &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; 31&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; 5&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; 2&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;3&lt;br /&gt;            Action = &lt;span class=&quot;token variable&quot;&gt;$action&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$actions&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Sort-Object&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Property MonthlyCost &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Descending &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Format-Table&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I hope this script helps you keep your Application Insights spend under control!&lt;/p&gt;
</description>
      <pubDate>Fri, 31 May 2019 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2019/05/keeping-application-insights-costs-under-control/</guid>
    </item>
    <item>
      <title>Should I Automate It?</title>
      <link>https://tjaddison.com/blog/2019/04/should-i-automate-it/</link>
      <description>&lt;p&gt;Whenever you need to do something more than once it&#39;s often tempting to invest in the process - either by making it easier to repeat or fully automating it.&lt;/p&gt;
&lt;p&gt;This post isn&#39;t about convincing you that it&#39;s time to automate that thing (if you&#39;re not already &#39;automate by default&#39; go check out &lt;a href=&quot;https://xkcd.com/1205/&quot;&gt;XKCD 1205&lt;/a&gt; - once you are fully on the automation train come back here). This post is about giving you another tool to help decide to &lt;em&gt;not automate&lt;/em&gt; that thing.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2019/04/should-i-automate-it/7mox1ttWNT-295.avif 295w, https://tjaddison.com/blog/2019/04/should-i-automate-it/7mox1ttWNT-590.avif 590w, https://tjaddison.com/blog/2019/04/should-i-automate-it/7mox1ttWNT-885.avif 885w, https://tjaddison.com/blog/2019/04/should-i-automate-it/7mox1ttWNT-1180.avif 1180w, https://tjaddison.com/blog/2019/04/should-i-automate-it/7mox1ttWNT-1475.avif 1475w, https://tjaddison.com/blog/2019/04/should-i-automate-it/7mox1ttWNT-1770.avif 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2019/04/should-i-automate-it/7mox1ttWNT-295.webp 295w, https://tjaddison.com/blog/2019/04/should-i-automate-it/7mox1ttWNT-590.webp 590w, https://tjaddison.com/blog/2019/04/should-i-automate-it/7mox1ttWNT-885.webp 885w, https://tjaddison.com/blog/2019/04/should-i-automate-it/7mox1ttWNT-1180.webp 1180w, https://tjaddison.com/blog/2019/04/should-i-automate-it/7mox1ttWNT-1475.webp 1475w, https://tjaddison.com/blog/2019/04/should-i-automate-it/7mox1ttWNT-1770.webp 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2019/04/should-i-automate-it/7mox1ttWNT-295.jpeg 295w, https://tjaddison.com/blog/2019/04/should-i-automate-it/7mox1ttWNT-590.jpeg 590w, https://tjaddison.com/blog/2019/04/should-i-automate-it/7mox1ttWNT-885.jpeg 885w, https://tjaddison.com/blog/2019/04/should-i-automate-it/7mox1ttWNT-1180.jpeg 1180w, https://tjaddison.com/blog/2019/04/should-i-automate-it/7mox1ttWNT-1475.jpeg 1475w, https://tjaddison.com/blog/2019/04/should-i-automate-it/7mox1ttWNT-1770.jpeg 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Automation Calculator&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2019/04/should-i-automate-it/7mox1ttWNT-295.jpeg&quot; width=&quot;1770&quot; height=&quot;1015&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Download the &lt;a href=&quot;https://tjaddison.com/blog/2019/04/should-i-automate-it/SampleWorkbook.xlsx&quot;&gt;Calculator&lt;/a&gt; to follow along!&lt;/p&gt;
&lt;h2 id=&quot;the-weekly-health-check&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/04/should-i-automate-it/#the-weekly-health-check&quot;&gt;The weekly health check&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let&#39;s imagine that every week you have a few SQL Servers you run a health check on - you grab a bit of data on key metrics, compare to a baseline number, and then send a quick summary to the team highlighting anywhere the servers need some attention this week. Let&#39;s say it currently takes you 30 minutes, and you do it every week.&lt;/p&gt;
&lt;p&gt;Ignoring vacations that&#39;s 1,560 minutes a year - almost 26 hours spent pulling data! And if you imagine this check is something you&#39;ll do forever (which we&#39;ll say is...5 years) then that comes out to just over 3 weeks of time spent on that task alone (making some assumptions about working weeks). Surely we should get on automating that ASAP?&lt;/p&gt;
&lt;h2 id=&quot;how-long-is-that-automation-going-to-take&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/04/should-i-automate-it/#how-long-is-that-automation-going-to-take&quot;&gt;How long is that automation going to take?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the prior step we took our requirement, it&#39;s duration and frequency, and a time frame - and then used those to come up with an &lt;em&gt;automation budget&lt;/em&gt;. Although many projects end up with arbitrary deadlines I&#39;ve rarely found them to be delivered feature-complete or on-time. For automation I&#39;d suggest by focusing on the question &lt;em&gt;how long will it take to automate&lt;/em&gt;?&lt;/p&gt;
&lt;p&gt;This is a pretty hard question to answer! Depending on the task, the amount to which you automate, and your expertise at the automation process the answer will vary wildly. Rather than focusing on automating the whole thing it&#39;s often better to focus on how much &lt;em&gt;time you can save&lt;/em&gt; by automating part of a task, and then compute your Return On Investment (ROI) for that piece of automation.&lt;/p&gt;
&lt;p&gt;Let&#39;s assume that we&#39;ll start by automating the collection of metrics - in our example we were connecting to each server manually to run a query, and we&#39;re going to build something to allow us to run our query against multiple servers at the same time. That&#39;s a fairly tractable project, we&#39;re pretty experienced with it, so we say it&#39;ll take &lt;em&gt;4 hours&lt;/em&gt; to implement that, which &lt;em&gt;once every week&lt;/em&gt; will save us &lt;em&gt;5 minutes&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;If we plug those into the &lt;a href=&quot;https://tjaddison.com/blog/2019/04/should-i-automate-it/SampleWorkbook.xlsx&quot;&gt;Calculator&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Time to automate - 240&lt;/li&gt;
&lt;li&gt;Time saved by automation - 5&lt;/li&gt;
&lt;li&gt;Times per month - 4 (once per week)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;It&#39;s going to take 1 year before the automation investment pays off.&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The ROI column tells you how much time you&#39;ve saved less how much you spent on automation - less than zero means you&#39;re in &#39;automation debt&#39;, and more than zero means your automation is paying itself back.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;estimates-are-approximate&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/04/should-i-automate-it/#estimates-are-approximate&quot;&gt;Estimates are...approximate&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Most of us really suck at estimates. I&#39;ve found it extremely helpful to always look at automation based on a range. The calculator defaults to assuming that the best-case might be up to twice as fast - instead of 240 minutes it&#39;ll only take 120. It also defaults to assuming your worst case is up to three times slower - so that 240 minutes becomes 480 minutes. Ouch.&lt;/p&gt;
&lt;p&gt;These multipliers (and you can adjust them) are what drive the &lt;code&gt;ROI Fast&lt;/code&gt; and &lt;code&gt;ROI Slow&lt;/code&gt; columns. You can see that in the best case (we automate faster than we thought) we get payback after only 9 months, whereas in the slow case we&#39;re waiting up to 2 years.&lt;/p&gt;
&lt;p&gt;The results of this automation show that this automation has a long-term payoff - 1-2 years before we land in the definitely bucket (which is where even the &lt;code&gt;Slow ROI&lt;/code&gt; shows a payoff). If the process is likely to stay static and not require changes over the next 2 years then this might be worth automating?&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2019/04/should-i-automate-it/UTmhIN3xJd-295.avif 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2019/04/should-i-automate-it/UTmhIN3xJd-295.webp 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Automation Calculator&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2019/04/should-i-automate-it/UTmhIN3xJd-295.jpeg&quot; width=&quot;295&quot; height=&quot;136&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h2 id=&quot;so-should-i-automate-it&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/04/should-i-automate-it/#so-should-i-automate-it&quot;&gt;So, should I automate it?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This tool allows you to quickly see how long before your estimate pays off. Work prioritisation is ultimately about saying no to many different projects, and if you find out after plugging your numbers in that the payback is in years then you might be able to say no quite quickly.&lt;/p&gt;
&lt;p&gt;It&#39;s pretty rare you&#39;ll ever make a decision solely based on the numbers here - I&#39;ve found the following to be useful (though not an exhaustive) set of inputs into deciding if the automation is worth it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Does it &lt;em&gt;totally&lt;/em&gt; automate a specific task, removing the need for any manual intervention? This is slightly more valuable than something that is partially automated.&lt;/li&gt;
&lt;li&gt;How static is this process, and how often do we expect it to change?&lt;/li&gt;
&lt;li&gt;Are there any easier ways to save the time (stop doing the task, delegate it, etc.)&lt;/li&gt;
&lt;li&gt;Does it help learn/share a new skill?&lt;/li&gt;
&lt;li&gt;Does it automate something that is particularly error-prone when completed manually (increasing accuracy)?&lt;/li&gt;
&lt;li&gt;How easy is it to maintain/debug the automation process?&lt;/li&gt;
&lt;li&gt;Does it create some kind of technical leverage (automating data collection from multiple servers would have uses outside of this specific task, etc.)&lt;/li&gt;
&lt;li&gt;Is saving that time the highest value-add right now (if you instead spend those 5 minutes doing it manually what else could you be working on)?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And that last one is really key, especially when you get into larger projects that might take days or even weeks. Every automation effort invariably pays off over some time period, but whether you could be doing something even more valuable with your time is where the really hard work of prioritisation comes in. Hopefully the calculator will help you decide that automation project you were considering isn&#39;t worth putting into that list of &#39;things that need to be prioritised&#39;, and give you one less thing to worry about.&lt;/p&gt;
</description>
      <pubDate>Tue, 30 Apr 2019 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2019/04/should-i-automate-it/</guid>
    </item>
    <item>
      <title>Understanding space usage in Azure Monitor logs</title>
      <link>https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/</link>
      <description>&lt;p&gt;Data ingested to &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-monitor/platform/data-platform-logs&quot;&gt;Azure Monitor logs&lt;/a&gt; is billed per-Gigabyte ingested. As a workspace will typically grow to have data coming from many different sources and solutions it is helpful to have a set of queries that allow you to quickly drill into where exactly the GBs (or TBs!) of data you have stored comes from.&lt;/p&gt;
&lt;p&gt;I&#39;ve found the below queries very helpful starting points for three main scenarios:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Regular monitoring (once/month) to see how data volumes are trending&lt;/li&gt;
&lt;li&gt;Reacting to a monitoring alert based on overall ingestion volumes&lt;/li&gt;
&lt;li&gt;Testing out a configuration change/new solution and observing the impact on data ingested&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The latter is particularly important before rolling a change to a workspace with long retention - you wouldn&#39;t want (hypothetically :)) to accidentally ingest 100GB of IIS logs and then be forced to retain them for 2 years...&lt;/p&gt;
&lt;h2 id=&quot;workspace-solution-data-type-usage&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/#workspace-solution-data-type-usage&quot;&gt;Workspace solution/data type usage&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This query provides a summary of the top solutions and data types in a single workspace, along with how they contribute to overall usage. If your workspace has a lot of different solutions or data types you&#39;ll benefit from changing the values passed to the &lt;code&gt;top-nested&lt;/code&gt; operators.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Usage
| where TimeGenerated &amp;gt; startofday(ago(30d)) and TimeGenerated &amp;lt; startofday(now())
| where IsBillable
| top-nested 1 of &#39;All&#39; by AllData = round(sum(Quantity),0)
, top-nested 2 of Solution with others=&#39;Others&#39; by SolutionTotal = round(sum(Quantity), 0)
, top-nested 2 of DataType with others = &#39;Others&#39; by SolutionDataTotal = round(sum(Quantity), 0)
| where AllData != 0
| extend SolutionPct = round((SolutionDataTotal / SolutionTotal) * 100, 1)
| extend OverallPct = round((SolutionDataTotal / AllData) * 100,1)
| project AllData, SolutionTotal, Solution, DataType, SolutionPct, SolutionDataTotal, OverallPct
| order by OverallPct desc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A couple of example results are below - one for a security focused workspace, and one for a workspace which has a bit more going on.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_e6I6Hwx0U-295.avif 295w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_e6I6Hwx0U-590.avif 590w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_e6I6Hwx0U-885.avif 885w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_e6I6Hwx0U-1180.avif 1180w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_e6I6Hwx0U-1475.avif 1475w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_e6I6Hwx0U-295.webp 295w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_e6I6Hwx0U-590.webp 590w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_e6I6Hwx0U-885.webp 885w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_e6I6Hwx0U-1180.webp 1180w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_e6I6Hwx0U-1475.webp 1475w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_e6I6Hwx0U-295.jpeg 295w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_e6I6Hwx0U-590.jpeg 590w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_e6I6Hwx0U-885.jpeg 885w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_e6I6Hwx0U-1180.jpeg 1180w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_e6I6Hwx0U-1475.jpeg 1475w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Security Workspace&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_e6I6Hwx0U-295.jpeg&quot; width=&quot;1475&quot; height=&quot;416&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;We can see here that this workspace is dominated by the Security event log - almost 70% of all the data is the &lt;code&gt;SecurityEvent&lt;/code&gt; data type in the &lt;code&gt;Security&lt;/code&gt; solution. This corresponds to the Windows Security Event Log records that are uploaded.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/lA_k4vMSK3-295.avif 295w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/lA_k4vMSK3-590.avif 590w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/lA_k4vMSK3-885.avif 885w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/lA_k4vMSK3-1180.avif 1180w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/lA_k4vMSK3-1475.avif 1475w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/lA_k4vMSK3-1770.avif 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/lA_k4vMSK3-295.webp 295w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/lA_k4vMSK3-590.webp 590w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/lA_k4vMSK3-885.webp 885w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/lA_k4vMSK3-1180.webp 1180w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/lA_k4vMSK3-1475.webp 1475w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/lA_k4vMSK3-1770.webp 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/lA_k4vMSK3-295.jpeg 295w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/lA_k4vMSK3-590.jpeg 590w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/lA_k4vMSK3-885.jpeg 885w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/lA_k4vMSK3-1180.jpeg 1180w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/lA_k4vMSK3-1475.jpeg 1475w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/lA_k4vMSK3-1770.jpeg 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Complex Workspace&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/lA_k4vMSK3-295.jpeg&quot; width=&quot;1770&quot; height=&quot;415&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;This workspace demonstrates how the &lt;code&gt;top-nested&lt;/code&gt; operator works. Inside of the LogManagement solution we&#39;ve got 2 distinct data types (Perf and a Custom Log - the one ending in _CL). We&#39;ve also got the &#39;Others&#39; record which tells us that the longer tail of data types in that solution might be worth looking at as they comprise nearly 30% of the solution total, and 15% of overall data.&lt;/p&gt;
&lt;h2 id=&quot;workspace-data-over-time&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/#workspace-data-over-time&quot;&gt;Workspace data over time&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you have multiple workspaces it can be helpful to see how their data trends over time. Extending this query to run over additional workspaces is unfortunately a matter of copy-paste:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;workspace(&amp;quot;primaryWorkspace&amp;quot;).Usage
| where TimeGenerated &amp;gt; startofday(ago(30d)) and TimeGenerated &amp;lt; startofday(now())
| where IsBillable
| extend Workspace = &amp;quot;Primary&amp;quot;
| summarize sum(Quantity) by Solution, bin(TimeGenerated, 1d), DataType, Workspace
| union (
workspace(&amp;quot;secondaryWorkspace&amp;quot;).Usage
| where TimeGenerated &amp;gt; startofday(ago(30d)) and TimeGenerated &amp;lt; startofday(now())
| where IsBillable
| extend Workspace = &amp;quot;Secondary&amp;quot;
| summarize sum(Quantity) by Solution, bin(TimeGenerated, 1d), DataType, Workspace
)
| render timechart with(title = &#39;Workspace Data Usage&#39;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example the primary workspace is responsible for capturing data which will be retained for a long period of time, and the secondary workspace captures higher volume data. Use the dropdown to change between viewing by Workspace, Solution or Data Type.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_NcyZxeUuI-295.avif 295w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_NcyZxeUuI-590.avif 590w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_NcyZxeUuI-885.avif 885w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_NcyZxeUuI-1180.avif 1180w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_NcyZxeUuI-1475.avif 1475w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_NcyZxeUuI-1770.avif 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_NcyZxeUuI-295.webp 295w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_NcyZxeUuI-590.webp 590w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_NcyZxeUuI-885.webp 885w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_NcyZxeUuI-1180.webp 1180w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_NcyZxeUuI-1475.webp 1475w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_NcyZxeUuI-1770.webp 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_NcyZxeUuI-295.jpeg 295w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_NcyZxeUuI-590.jpeg 590w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_NcyZxeUuI-885.jpeg 885w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_NcyZxeUuI-1180.jpeg 1180w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_NcyZxeUuI-1475.jpeg 1475w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_NcyZxeUuI-1770.jpeg 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Data usage over time&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/_NcyZxeUuI-295.jpeg&quot; width=&quot;1770&quot; height=&quot;776&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note the usage of &lt;code&gt;startofday(...)&lt;/code&gt; when filtering on &lt;code&gt;TimeGenerated&lt;/code&gt;. Cutting boundaries at midnight at the start/end removes weird artefacts where the graph looks like it is unusually low due to only part of a day&#39;s data being counted.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;drilling-into-a-single-data-type&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/#drilling-into-a-single-data-type&quot;&gt;Drilling into a single data type&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At some point a graph will invariably point to something that looks a little weird - an upward trend or maybe a huge spike - and you&#39;ll want to understand where that is coming from. The good news is Azure Monitor logs provides a pair of virtual columns &lt;code&gt;_IsBillable&lt;/code&gt; and &lt;code&gt;_BilledSize&lt;/code&gt; that let you drill in to any data type and find out exactly what it is costing you.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Event
| where TimeGenerated &amp;gt; startofday(ago(30d)) and TimeGenerated &amp;lt; startofday(now())
| where _IsBillable == true
| extend computerName = tolower(tostring(split(Computer, &#39;.&#39;)[0]))
| where computerName != &amp;quot;&amp;quot;
| summarize TotalVolumeMB = sum(_BilledSize / 1024 / 1024) by computerName, bin(TimeGenerated, 1d)
| render barchart with(title = &#39;EventLog by Computer&#39;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example assume we discovered the &lt;code&gt;Event&lt;/code&gt; data type in a particular workspace had a spike - running the query should tell us quickly if this was limited to a single server or all servers. And as you can see below there were three servers that had a very &lt;em&gt;eventful&lt;/em&gt; day a little while back!&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/FLICCgmQY_-295.avif 295w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/FLICCgmQY_-590.avif 590w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/FLICCgmQY_-885.avif 885w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/FLICCgmQY_-1180.avif 1180w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/FLICCgmQY_-1475.avif 1475w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/FLICCgmQY_-1770.avif 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/FLICCgmQY_-295.webp 295w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/FLICCgmQY_-590.webp 590w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/FLICCgmQY_-885.webp 885w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/FLICCgmQY_-1180.webp 1180w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/FLICCgmQY_-1475.webp 1475w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/FLICCgmQY_-1770.webp 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/FLICCgmQY_-295.jpeg 295w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/FLICCgmQY_-590.jpeg 590w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/FLICCgmQY_-885.jpeg 885w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/FLICCgmQY_-1180.jpeg 1180w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/FLICCgmQY_-1475.jpeg 1475w, https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/FLICCgmQY_-1770.jpeg 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;EventLog by computer&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/FLICCgmQY_-295.jpeg&quot; width=&quot;1770&quot; height=&quot;828&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h2 id=&quot;further-reading&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/#further-reading&quot;&gt;Further reading&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-monitor/platform/manage-cost-storage&quot;&gt;official docs&lt;/a&gt; are a great place to start reading more about space usage and monitoring for Azure Monitor logs. I&#39;ve also put a &lt;a href=&quot;https://github.com/taddison/kql-queries/blob/master/log-analytics-usage.md&quot;&gt;couple of queries on GitHub&lt;/a&gt; that I&#39;ll update over time as I find any better ways of cutting this data.&lt;/p&gt;
</description>
      <pubDate>Sun, 31 Mar 2019 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2019/03/understanding-space-usage-in-azure-monitor-logs/</guid>
    </item>
    <item>
      <title>Auto scale down all Event Hub namespaces with Azure Functions</title>
      <link>https://tjaddison.com/blog/2019/02/auto-scale-down-all-event-hub-namespaces-with-azure-functions/</link>
      <description>&lt;p&gt;A little over a year ago I lamented the lack of an auto-deflate feature for Event Hubs, and offered a way to &lt;a href=&quot;https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/&quot;&gt;programmatically scale down your namespaces&lt;/a&gt;. That solution still works, but requires a redeploy each time you wanted to add a namespace. Today we&#39;ll look at an upgraded function app which programmatically discovers and scales-down all Event Hub namespaces it has access to.&lt;/p&gt;
&lt;p&gt;With the addition of a PowerShell script to grant the appropriate permissions to all of your namespaces, you can be up and running (or deflating) in a matter of minutes.&lt;/p&gt;
&lt;h2 id=&quot;function-app&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/02/auto-scale-down-all-event-hub-namespaces-with-azure-functions/#function-app&quot;&gt;Function App&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The source code for the function app can be found on GitHub in the &lt;a href=&quot;https://github.com/taddison/ScaleDownEventHubs&quot;&gt;ScaleDownEventHubs repo&lt;/a&gt;. This is an Azure Functions v2 app which is by default configured to run every six hours. When executed the function will:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Discover all Event Hub namespaces it has access to&lt;/li&gt;
&lt;li&gt;Query each namespace to check if auto-inflate is enabled
&lt;ul&gt;
&lt;li&gt;If auto-inflate is disabled it skips this namespace (assume that the throughput units should be static)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Query each namespace for a tag called &lt;code&gt;ScaleDownTUs&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;If this tag is present the value is used as the target to scale down to, if it is not present then the default is 1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Compare the current capacity (Throughput Units) to the target, and if the capacity is higher than the target then reduce the capacity&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In order to &lt;em&gt;function&lt;/em&gt; (no pun intended!) the app requires the following three variables to be available - this would typically be in the form of an application setting:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; clientId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ClientId&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; clientSecret &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ClientSecret&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; tenantId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;TenantId&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;TenantId&lt;/code&gt; is your Azure Active Directory tenant. The other values come from the service principal you use to run the app, which we&#39;ll create in the next section.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2019/02/auto-scale-down-all-event-hub-namespaces-with-azure-functions/gr-9AsHlho-295.avif 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2019/02/auto-scale-down-all-event-hub-namespaces-with-azure-functions/gr-9AsHlho-295.webp 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;App settings after deployment&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2019/02/auto-scale-down-all-event-hub-namespaces-with-azure-functions/gr-9AsHlho-295.jpeg&quot; width=&quot;295&quot; height=&quot;275&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that the function does quite a lot of logging - if you configure Application Insights for your function app you&#39;ll be able to monitor what your function gets up to over time&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;service-principal&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/02/auto-scale-down-all-event-hub-namespaces-with-azure-functions/#service-principal&quot;&gt;Service Principal&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The script below will create a &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/active-directory/develop/app-objects-and-service-principals&quot;&gt;service principal&lt;/a&gt; - this is what we&#39;ll be granting permissions to so that it can perform operations on our Event Hub namespaces.&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;Login-AzAccount&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$sp&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;New-AzADServicePrincipal&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;DisplayName &lt;span class=&quot;token string&quot;&gt;&quot;EventHubScaler&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$applicationId&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$sp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ApplicationId&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$BSTR&lt;/span&gt; = &lt;span class=&quot;token namespace&quot;&gt;[System.Runtime.InteropServices.Marshal]&lt;/span&gt;::SecureStringToBSTR&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$sp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Secret&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$password&lt;/span&gt; = &lt;span class=&quot;token namespace&quot;&gt;[System.Runtime.InteropServices.Marshal]&lt;/span&gt;::PtrToStringAuto&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$BSTR&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token namespace&quot;&gt;[Runtime.InteropServices.Marshal]&lt;/span&gt;::ZeroFreeBSTR&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$BSTR&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Write-Output&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ClientId: &lt;span class=&quot;token variable&quot;&gt;$applicationId&lt;/span&gt;&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Write-Output&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ClientSecret: &lt;span class=&quot;token variable&quot;&gt;$password&lt;/span&gt;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you set the &lt;code&gt;ClientId&lt;/code&gt; and &lt;code&gt;ClientSecret&lt;/code&gt; application settings you&#39;ll now be able to run your function app. If you want to test locally you can put these into a file called &lt;code&gt;local.settings.json&lt;/code&gt; - an example is included in the function&#39;s git repo (&lt;code&gt;local.settings.json.example&lt;/code&gt;).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note when testing locally you&#39;ll want to change the cron string of the &lt;code&gt;TimerTrigger&lt;/code&gt; to something more frequent than every six hours - using 0 _/1 _ * * will have the function run every minute.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;permissions&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/02/auto-scale-down-all-event-hub-namespaces-with-azure-functions/#permissions&quot;&gt;Permissions&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;By default the new service principal has no permissions. In order to let it do something useful we&#39;re going to assign it &lt;code&gt;Contributor&lt;/code&gt; permissions on all namespaces we have access to.&lt;/p&gt;
&lt;p&gt;The below script looks pretty daunting, though by default it will do nothing as &lt;code&gt;$WhatIf&lt;/code&gt; is set to &lt;code&gt;$true&lt;/code&gt; - when running the script it will echo what it &lt;em&gt;would&lt;/em&gt; do. If there is no work to do it will echo the status of each Event Hub namespace it discovers, as shown in the screenshot below. When you&#39;re ready to add permissions set &lt;code&gt;$WhatIf&lt;/code&gt; to &lt;code&gt;$false&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This script will only assign the service principal to namespaces which have auto-inflate set to true.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2019/02/auto-scale-down-all-event-hub-namespaces-with-azure-functions/wljfHeZ155-295.avif 295w, https://tjaddison.com/blog/2019/02/auto-scale-down-all-event-hub-namespaces-with-azure-functions/wljfHeZ155-590.avif 590w, https://tjaddison.com/blog/2019/02/auto-scale-down-all-event-hub-namespaces-with-azure-functions/wljfHeZ155-885.avif 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2019/02/auto-scale-down-all-event-hub-namespaces-with-azure-functions/wljfHeZ155-295.webp 295w, https://tjaddison.com/blog/2019/02/auto-scale-down-all-event-hub-namespaces-with-azure-functions/wljfHeZ155-590.webp 590w, https://tjaddison.com/blog/2019/02/auto-scale-down-all-event-hub-namespaces-with-azure-functions/wljfHeZ155-885.webp 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2019/02/auto-scale-down-all-event-hub-namespaces-with-azure-functions/wljfHeZ155-295.jpeg 295w, https://tjaddison.com/blog/2019/02/auto-scale-down-all-event-hub-namespaces-with-azure-functions/wljfHeZ155-590.jpeg 590w, https://tjaddison.com/blog/2019/02/auto-scale-down-all-event-hub-namespaces-with-azure-functions/wljfHeZ155-885.jpeg 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;App settings after deployment&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2019/02/auto-scale-down-all-event-hub-namespaces-with-azure-functions/wljfHeZ155-295.jpeg&quot; width=&quot;885&quot; height=&quot;392&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;Login-AzAccount&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$appName&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;EventHubScaler&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$appRole&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;Contributor&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$WhatIf&lt;/span&gt; = &lt;span class=&quot;token boolean&quot;&gt;$true&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# set to false to add role&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$applicationId&lt;/span&gt; = &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Get-AzADServicePrincipal&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;DisplayName &lt;span class=&quot;token variable&quot;&gt;$appName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ApplicationId&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$subs&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-AzSubscription&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$sub&lt;/span&gt; in &lt;span class=&quot;token variable&quot;&gt;$subs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;Set-AzContext&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$sub&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Out-Null&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;Write-Output&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Context set to &lt;span class=&quot;token function&quot;&gt;$&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$sub&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$hubs&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-AzEventHubNamespace&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$hub&lt;/span&gt; in &lt;span class=&quot;token variable&quot;&gt;$hubs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$hubName&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$hub&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$capacity&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$hub&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Sku&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Capacity&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$autoInflate&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$hub&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;IsAutoInflateEnabled&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$maxCapacity&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$hub&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MaximumThroughputUnits&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$assignments&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-AzRoleAssignment&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Scope &lt;span class=&quot;token variable&quot;&gt;$hub&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Id `&lt;br /&gt;            &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;RoleDefinitionName &lt;span class=&quot;token variable&quot;&gt;$appRole&lt;/span&gt; `&lt;br /&gt;            &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ServicePrincipalName &lt;span class=&quot;token variable&quot;&gt;$applicationId&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-eq&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$assignments&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token variable&quot;&gt;$assignString&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token variable&quot;&gt;$assignString&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;[&lt;span class=&quot;token variable&quot;&gt;$appName&lt;/span&gt; ASSIGNED]&quot;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$autoInflate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token function&quot;&gt;Write-Output&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Namespace:&lt;span class=&quot;token variable&quot;&gt;$hubName&lt;/span&gt; :: TU:&lt;span class=&quot;token variable&quot;&gt;$capacity&lt;/span&gt;/&lt;span class=&quot;token variable&quot;&gt;$maxCapacity&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$scaleDownTUs&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$assignString&lt;/span&gt;&quot;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-eq&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$assignments&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$WhatIf&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-eq&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;$false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                    &lt;span class=&quot;token function&quot;&gt;Write-Output&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Adding &lt;span class=&quot;token variable&quot;&gt;$appRole&lt;/span&gt; role for &lt;span class=&quot;token variable&quot;&gt;$appName&lt;/span&gt; on &lt;span class=&quot;token variable&quot;&gt;$hubName&lt;/span&gt;&quot;&lt;/span&gt;&lt;br /&gt;                    &lt;span class=&quot;token function&quot;&gt;New-AzRoleAssignment&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Scope &lt;span class=&quot;token variable&quot;&gt;$hub&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Id `&lt;br /&gt;                        &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;RoleDefinitionName &lt;span class=&quot;token variable&quot;&gt;$appRole&lt;/span&gt; `&lt;br /&gt;                        &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ApplicationId &lt;span class=&quot;token variable&quot;&gt;$applicationId&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Out-Null&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                    &lt;span class=&quot;token function&quot;&gt;Write-Output&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;[WHATIF] Adding &lt;span class=&quot;token variable&quot;&gt;$appRole&lt;/span&gt; role for &lt;span class=&quot;token variable&quot;&gt;$appName&lt;/span&gt; on &lt;span class=&quot;token variable&quot;&gt;$hubName&lt;/span&gt;&quot;&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token function&quot;&gt;Write-Output&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Namespace:&lt;span class=&quot;token variable&quot;&gt;$hubName&lt;/span&gt; :: TU:&lt;span class=&quot;token variable&quot;&gt;$capacity&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$assignString&lt;/span&gt;&quot;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/02/auto-scale-down-all-event-hub-namespaces-with-azure-functions/#conclusion&quot;&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you deployed Application Insights you&#39;ll be able to see what your scaler is up to over time with a query like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;traces
| where message startswith &amp;quot;Updating&amp;quot;
| parse message with &amp;quot;Updating Namespace:&amp;quot; namespace:string &amp;quot;in RG:&amp;quot; resourcegroup:string &amp;quot;from:&amp;quot; fromTU:int  &amp;quot;to:&amp;quot; toTU:int
| extend Op = &amp;quot;ScaleDown&amp;quot;
| union (
traces
| where message startswith &amp;quot;Namespace:&amp;quot;
| extend Op = &amp;quot;&amp;quot;
| parse message with &amp;quot;Namespace:&amp;quot; namespace:string &amp;quot;in RG:&amp;quot; resourcegroup:string &amp;quot;already at or below target capacity (Current:&amp;quot; fromTU:int  &amp;quot;Target:&amp;quot; toTU:int *
)
| order by timestamp desc
| project timestamp, Op, resourcegroup, namespace, fromTU, toTU
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One example result set is shown below - this shows multiple namespaces surviving the scaler unscathed (unscaled?), and a couple which were scaled-in. You can also see that there a few namespaces which don&#39;t scale down to 1 - these have &lt;code&gt;ScaleDownTUs&lt;/code&gt; set on them.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2019/02/auto-scale-down-all-event-hub-namespaces-with-azure-functions/wfEkldplWN-295.avif 295w, https://tjaddison.com/blog/2019/02/auto-scale-down-all-event-hub-namespaces-with-azure-functions/wfEkldplWN-590.avif 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2019/02/auto-scale-down-all-event-hub-namespaces-with-azure-functions/wfEkldplWN-295.webp 295w, https://tjaddison.com/blog/2019/02/auto-scale-down-all-event-hub-namespaces-with-azure-functions/wfEkldplWN-590.webp 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2019/02/auto-scale-down-all-event-hub-namespaces-with-azure-functions/wfEkldplWN-295.jpeg 295w, https://tjaddison.com/blog/2019/02/auto-scale-down-all-event-hub-namespaces-with-azure-functions/wfEkldplWN-590.jpeg 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Scaler query result&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2019/02/auto-scale-down-all-event-hub-namespaces-with-azure-functions/wfEkldplWN-295.jpeg&quot; width=&quot;590&quot; height=&quot;413&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
</description>
      <pubDate>Thu, 28 Feb 2019 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2019/02/auto-scale-down-all-event-hub-namespaces-with-azure-functions/</guid>
    </item>
    <item>
      <title>Overhead of Event Hub outputs with Azure Function Apps</title>
      <link>https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/</link>
      <description>&lt;p&gt;In a world where you&#39;re billed for what you use, it pays to really understand what &lt;em&gt;exactly&lt;/em&gt; it is you are using. The &lt;a href=&quot;https://azure.microsoft.com/en-us/pricing/details/functions/&quot;&gt;pricing model&lt;/a&gt; of the Azure Function Apps consumption plan sounds pretty simple (pay based on execution count and execution time) - though as always the devil is in the detail.&lt;/p&gt;
&lt;p&gt;In a recent project where we&#39;ve migrated a workload from a dedicated (on-prem) server to a function app, someone asked what sounded like a fairly simple question:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Do we pay (on the function app side) for the output to an Event Hub, and if so how much does it cost?&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This post explores the answer to that question for a trivial C# function, and provides a few pointers to help get your head around consumption billing.&lt;/p&gt;
&lt;p&gt;If you&#39;re wondering why we wanted to deconstruct the function&#39;s cost when an &lt;em&gt;execution unit&lt;/em&gt; costs a mere 16 picodollars (16x10^-12), consider what we saw after our first week at full load:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/DVAbRtOsdb-295.avif 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/DVAbRtOsdb-295.webp 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;A lot of executions&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/DVAbRtOsdb-295.jpeg&quot; width=&quot;295&quot; height=&quot;126&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;5 trillion execution units... Interesting!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Execution units vs. what you see on the pricing page is covered later in the post.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;consumption-billing&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/#consumption-billing&quot;&gt;Consumption billing&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Functions hosted on the consumption plan charge you for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Every function execution ($0.20 per million execution)&lt;/li&gt;
&lt;li&gt;Every gigabyte-second used ($0.000016 per GBs)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the function runs for one second and uses 1GB of memory, your function has used 1GBs. The smallest quantum is 128MB for memory, and 1 millisecond for duration. Billing rounds up, so a function using 1MB for 10us is billed at 0.128GB x 1ms, a function using 129MB for 1001us is billed at 0.256GB for 2ms, etc.&lt;/p&gt;
&lt;p&gt;Execution time is measured from the start of the function to the end of the function - and is &lt;em&gt;not&lt;/em&gt; CPU time. If your function does any calls to external systems (like an Event Hub) that is billed. This includes the input/output bindings.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is one reason why your functions shouldn&#39;t call other functions - you&#39;ll get billed for the execution time of both! If you need to use this pattern then &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-overview&quot;&gt;Durable Functions&lt;/a&gt; might be a better fit, though prepare for a whole host of new billing complexities to wrap your head around.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Billing information is provided at the function-app level, and not per-function level.&lt;/p&gt;
&lt;p&gt;Azure Monitor doesn&#39;t expose the GBs billing metric directly, and instead exposes a metric called &lt;em&gt;Function Execution Units&lt;/em&gt;. One of these is equal to 1 megabyte millisecond. Converting from GBs to MBms can be done by dividing through by 1,024,000 (GB -&amp;gt; MB is 1024, s -&amp;gt; ms is 1000).&lt;/p&gt;
&lt;p&gt;All of these together tell us that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Any optimisation that doesn&#39;t help us cross a boundary of 128MB won&#39;t impact cost&lt;/li&gt;
&lt;li&gt;Any optimisation that doesn&#39;t help us cross a boundary of 1ms execution time wont&#39; impact cost&lt;/li&gt;
&lt;li&gt;Adding an Event Hub output is only &#39;free&#39; if it takes &amp;lt;1ms and has a memory impact that won&#39;t take us over a 128MB memory boundary&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;For more details on the consumption plan check out the &lt;a href=&quot;https://github.com/Azure/Azure-Functions/wiki/Consumption-Plan-Cost-Billing-FAQ&quot;&gt;Consumption FAQ&lt;/a&gt;. Precise details on how the memory usage is billed is pretty hard to pin down - there are a handful of outstanding issues that may need to be resolved before it&#39;s straightforward to examine (see &lt;a href=&quot;https://github.com/Azure/azure-functions-host/issues/3852&quot;&gt;here&lt;/a&gt;, &lt;a href=&quot;https://github.com/Azure/Azure-Functions/issues/726&quot;&gt;here&lt;/a&gt;, and &lt;a href=&quot;https://github.com/Azure/Azure-Functions/issues/762&quot;&gt;here&lt;/a&gt;).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;test-harness&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/#test-harness&quot;&gt;Test harness&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Our test harness is going to take a stock function (&lt;code&gt;return&lt;/code&gt;) and compare it to a function which outputs a message to an Event Hub.&lt;/p&gt;
&lt;p&gt;The functions below were deployed as separate function apps (to enable parallel testing and still get the granular cost metrics needed). You can view the &lt;a href=&quot;https://github.com/taddison/csharp-coffre/tree/master/function-app-eventhub-test&quot;&gt;sample project on GitHub&lt;/a&gt;. The deployment was a straightforward &#39;Publish&#39; from Visual Studio, with each function getting it&#39;s own storage account/app service plan (on the consumption tier), as well as an application insights resource. An Event Hub was created to act as the output binding for the second test.&lt;/p&gt;
&lt;h3 id=&quot;no-op-test&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/#no-op-test&quot;&gt;No-Op Test&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;FunctionName&lt;/span&gt;&lt;span class=&quot;token attribute-arguments&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;NoOp&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Task&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;IActionResult&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpTrigger&lt;/span&gt;&lt;span class=&quot;token attribute-arguments&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;AuthorizationLevel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Anonymous&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;post&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Route &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpRequest&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token class-name&quot;&gt;ILogger&lt;/span&gt; log&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;OkResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;event-hub-test&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/#event-hub-test&quot;&gt;Event Hub Test&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;FunctionName&lt;/span&gt;&lt;span class=&quot;token attribute-arguments&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;ToEventHub&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Task&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;IActionResult&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpTrigger&lt;/span&gt;&lt;span class=&quot;token attribute-arguments&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;AuthorizationLevel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Anonymous&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;post&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Route &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HttpRequest&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token class-name&quot;&gt;ILogger&lt;/span&gt; log&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;EventHub&lt;/span&gt;&lt;span class=&quot;token attribute-arguments&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token named-parameter punctuation&quot;&gt;eventHubName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;fatesteventhub&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Connection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;eh-connection&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ICollector&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; outMessages&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    outMessages&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello from the function app&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;OkResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;running-the-test&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/#running-the-test&quot;&gt;Running the Test&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This PowerShell script uses &lt;a href=&quot;https://blog.netnerds.net/2016/12/runspaces-simplified/&quot;&gt;runspaces&lt;/a&gt; to execute the tests in parallel. We&#39;re interested in executing the function a set number of times and examining cost (as opposed to load testing), and so any delay or &#39;unfairness&#39; due to the client-driver being slow isn&#39;t an issue.&lt;/p&gt;
&lt;p&gt;Once the code has finished executing we can then go to &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-monitor/overview&quot;&gt;Azure Monitor&lt;/a&gt; and pull the stats on execution count for both function apps and see what overhead there is.&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$BASELINE_URI&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;https://fa-eh-test-appinsights.azurewebsites.net/api/noop&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$EH_OUTPUT_URI&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;https://fa-eh-test-output.azurewebsites.net/api/toeventhub&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$REPEATS&lt;/span&gt; = 10000&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$tests&lt;/span&gt; = @&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    @&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Name = &lt;span class=&quot;token string&quot;&gt;&quot;Baseline&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; Uri = &lt;span class=&quot;token variable&quot;&gt;$BASELINE_URI&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    @&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Name = &lt;span class=&quot;token string&quot;&gt;&quot;EH Output&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; Uri = &lt;span class=&quot;token variable&quot;&gt;$EH_OUTPUT_URI&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$scriptblock&lt;/span&gt; = &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;Param&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$repeats&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;Write-Output&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Testing &lt;span class=&quot;token function&quot;&gt;$&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; for &lt;span class=&quot;token variable&quot;&gt;$repeats&lt;/span&gt; repeats at &lt;span class=&quot;token function&quot;&gt;$&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;[DateTime]&lt;/span&gt;::UtcNow&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$timings&lt;/span&gt; = @&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$i&lt;/span&gt; = 0&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$i&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-lt&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$repeats&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$i&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$timings&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Measure-Command&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token function&quot;&gt;Invoke-WebRequest&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Uri &lt;span class=&quot;token variable&quot;&gt;$test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Uri &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Method Post &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Out-Null&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TotalMilliseconds&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$summary&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$timings&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Measure-Object&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Average &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Maximum&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;Write-Output&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Finished &lt;span class=&quot;token function&quot;&gt;$&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; - Average &lt;span class=&quot;token function&quot;&gt;$&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$summary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Average&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; - Max &lt;span class=&quot;token function&quot;&gt;$&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$summary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Maximum&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; at &lt;span class=&quot;token function&quot;&gt;$&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;[DateTime]&lt;/span&gt;::UtcNow&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;## Thank you https://blog.netnerds.net/2016/12/runspaces-simplified/ !&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$pool&lt;/span&gt; = &lt;span class=&quot;token namespace&quot;&gt;[RunspaceFactory]&lt;/span&gt;::CreateRunspacePool&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;1&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$pool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Open&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$runspaces&lt;/span&gt; = @&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$tests&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ForEach-Object&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$runspace&lt;/span&gt; = &lt;span class=&quot;token namespace&quot;&gt;[PowerShell]&lt;/span&gt;::Create&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$null&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$runspace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AddScript&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$scriptblock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$null&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$runspace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AddArgument&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$_&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$null&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$runspace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AddArgument&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$repeats&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$runspace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;RunspacePool = &lt;span class=&quot;token variable&quot;&gt;$pool&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$runspaces&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;[PSCustomObject]&lt;/span&gt;@&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Pipe = &lt;span class=&quot;token variable&quot;&gt;$runspace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; Status = &lt;span class=&quot;token variable&quot;&gt;$runspace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;BeginInvoke&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$runspaces&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Status &lt;span class=&quot;token operator&quot;&gt;-ne&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$completed&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$runspaces&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Where-Object&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$_&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Status&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;IsCompleted &lt;span class=&quot;token operator&quot;&gt;-eq&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;$true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$runspace&lt;/span&gt; in &lt;span class=&quot;token variable&quot;&gt;$completed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$runspace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Pipe&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;EndInvoke&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$runspace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Status&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$runspace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Status = &lt;span class=&quot;token variable&quot;&gt;$null&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$pool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Close&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$pool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Dispose&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;results&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/#results&quot;&gt;Results&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After running the test multiple times the numbers were fairly consistent - the function with an Event Hub output binding cost about &lt;em&gt;18% more&lt;/em&gt; to run than a stock function.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/5U4jytFJEv-295.avif 295w, https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/5U4jytFJEv-590.avif 590w, https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/5U4jytFJEv-885.avif 885w, https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/5U4jytFJEv-1180.avif 1180w, https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/5U4jytFJEv-1475.avif 1475w, https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/5U4jytFJEv-1770.avif 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/5U4jytFJEv-295.webp 295w, https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/5U4jytFJEv-590.webp 590w, https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/5U4jytFJEv-885.webp 885w, https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/5U4jytFJEv-1180.webp 1180w, https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/5U4jytFJEv-1475.webp 1475w, https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/5U4jytFJEv-1770.webp 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/5U4jytFJEv-295.jpeg 295w, https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/5U4jytFJEv-590.jpeg 590w, https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/5U4jytFJEv-885.jpeg 885w, https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/5U4jytFJEv-1180.jpeg 1180w, https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/5U4jytFJEv-1475.jpeg 1475w, https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/5U4jytFJEv-1770.jpeg 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;With and without EventHub output&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/5U4jytFJEv-295.jpeg&quot; width=&quot;1770&quot; height=&quot;661&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;After crunching the raw numbers (which you can export from Azure Monitor to a CSV) we get to:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th style=&quot;text-align:right&quot;&gt;Execution Units&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stock&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;17,371&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Event Hub Output&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;20,500&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The addition of an Event Hub output was costing &lt;em&gt;3,129 execution units&lt;/em&gt;, or about &lt;strong&gt;$0.000000049 per execution&lt;/strong&gt;
(from the function app side - don&#39;t forget there&#39;s a whole set of billing concerns associated with the hub itself!).&lt;/p&gt;
&lt;p&gt;To put that into context here is what that translates to per-week in &lt;strong&gt;additional cost for the Event Hub output binding&lt;/strong&gt; for various requests per second:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;text-align:right&quot;&gt;RPS&lt;/th&gt;
&lt;th style=&quot;text-align:right&quot;&gt;Additional Weekly Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:right&quot;&gt;100&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;$2.96&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:right&quot;&gt;500&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;$14.78&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align:right&quot;&gt;1000&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;$29.57&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;It&#39;s worth emphasising that this is &lt;em&gt;additional cost&lt;/em&gt;, and that at high RPS the execution count component of cost starts to dominate. For a function being executed at 1000 RPS you&#39;d pay:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$164.15 in GBs for the stock function&lt;/li&gt;
&lt;li&gt;$29.58 &lt;em&gt;extra&lt;/em&gt; in GBs for the Event Hub output binding&lt;/li&gt;
&lt;li&gt;$120.96 for the execution count (1000 RPS is 605 million executions per week)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$314.69&lt;/strong&gt; in total&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&#39;s safe to say that when looking into cost that the presence of a binding itself is &lt;em&gt;not&lt;/em&gt; the thing to worry about. Over time as the hosting environment and functions runtime improve I would expect the cost associated with a stock function to go down. For very fast functions execution count will dominate cost.&lt;/p&gt;
&lt;h2 id=&quot;closing-thoughts&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/#closing-thoughts&quot;&gt;Closing Thoughts&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Cost-control in the cloud can be very... interesting? Knowing exactly how much you&#39;re spending is easy, but knowing what we&#39;re spending it on (and if that&#39;s the most efficient way to solve the problem)...another matter entirely.&lt;/p&gt;
&lt;p&gt;This post has scratched the surface of this topic - probably leaving you with more questions than answers. I haven&#39;t even got into considerations such as dedicated vs. consumption (some excellent coverage on the &lt;a href=&quot;https://www.azurefromthetrenches.com/azure-functions-scaling-with-a-dedicated-app-service-plan/&quot;&gt;Azure from the trenches blog&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;For our 5 trillion execution unit app we came to realise pretty quickly that the real issue is the architecture of our system - changing the client to reduce the number of messages sent (via batching) will deliver fair more value than than chasing down micro-optimisations in the function app. Some napkin math reveals we can probably get a 5-10x reduction in call count, and as we know executions dominate our cost that optimisation is falling straight to the bottom line.&lt;/p&gt;
&lt;p&gt;Understanding how the cloud services are built and how you&#39;re charged for using them is key in building the mental models required to architect a cost-effective deployment - the savings at scale can be quite significant.&lt;/p&gt;
</description>
      <pubDate>Thu, 31 Jan 2019 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2019/01/overhead-of-event-hub-outputs-with-azure-functions/</guid>
    </item>
    <item>
      <title>Adding caching to your PowerShell scripts</title>
      <link>https://tjaddison.com/blog/2018/12/adding-caching-to-your-powershell-scripts/</link>
      <description>&lt;p&gt;Most of the time when some part of a script takes a long time to run and you want to re-use the result you&#39;ll store it in a variable:&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$databases&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-ListOfDatabases&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# assume this is an expensive/long-running query&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Invoke-SomeOperation&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Server serverOne &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Databases &lt;span class=&quot;token variable&quot;&gt;$databases&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Invoke-SomeOtherOperation&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Server serverOne &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Databases &lt;span class=&quot;token variable&quot;&gt;$databases&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As long as the function you&#39;re calling allows you to pass in the &#39;expensive&#39; argument you&#39;re fine - but what about cases when the computation takes place inside the function:&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;Invoke-SomeOperation&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Server serverOne &lt;span class=&quot;token comment&quot;&gt;# internally calls Get-ListOfDatabases&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Invoke-SomeOtherOperation&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Server serverOne &lt;span class=&quot;token comment&quot;&gt;# internally calls Get-ListOfDatabases&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sometimes it makes sense to rework the script to accept the argument, though other times it can be cleaner to modify the call site to &lt;em&gt;cache&lt;/em&gt; the result, rather than changing every function to accept and potentially pass through that argument.&lt;/p&gt;
&lt;p&gt;The rest of this post will go through a specific example that motivated caching, share a generic function that implements scriptblock-based caching, and call out a few and gotchas.&lt;/p&gt;
&lt;h2 id=&quot;the-problem&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/12/adding-caching-to-your-powershell-scripts/#the-problem&quot;&gt;The Problem&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/taddison/SQLChecks&quot;&gt;SQLChecks&lt;/a&gt; library contains a set of &lt;a href=&quot;https://github.com/pester/Pester&quot;&gt;Pester&lt;/a&gt; tests that are designed to compare expected configuration against actual configuration. The first step of any per-database check is to query the instance (&lt;code&gt;Get-DatabasesToCheck&lt;/code&gt;) and get a list of databases to test. This query can end up being very slow (tens of seconds) when the server has a lot of availability group databases.&lt;/p&gt;
&lt;p&gt;The typical invocation looks something like this (simplified for clarity):&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$config&lt;/span&gt; in &lt;span class=&quot;token function&quot;&gt;Get-SqlChecksConfigs&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Path &lt;span class=&quot;token string&quot;&gt;&quot;...&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$check&lt;/span&gt; in &lt;span class=&quot;token function&quot;&gt;Get-SqlChecks&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;Invoke-Pester&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Check &lt;span class=&quot;token variable&quot;&gt;$check&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Config &lt;span class=&quot;token variable&quot;&gt;$config&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# calls Get-DatabasesToCheck&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;This pattern is used to enable test results to be sent to Log Analytics as each test completes, rather than waiting for the whole batch to finish. If we weren&#39;t looping through tests each Pester test file would only be called once, and the performance issue wouldn&#39;t exist.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For each &lt;code&gt;$config&lt;/code&gt;-&lt;code&gt;$check&lt;/code&gt; combination we&#39;ll end up calling the &lt;code&gt;Get-DatabasesToCheck&lt;/code&gt; function. The resulting list of databases shouldn&#39;t change between checks, so without caching we can end up spending a lot of time waiting to build the list of databases, scaling linearly as we add more checks/more databases.&lt;/p&gt;
&lt;p&gt;The wrapper code to run the tests is fairly generic, and supports running tests against the server, the SQL instance, or databases (and potentially other facets in the future). To keep the wrapper code generic &lt;em&gt;and&lt;/em&gt; make things performant, I added caching inside the &lt;code&gt;Get-DatabasesToCheck&lt;/code&gt; function.&lt;/p&gt;
&lt;h2 id=&quot;adding-caching&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/12/adding-caching-to-your-powershell-scripts/#adding-caching&quot;&gt;Adding Caching&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I wanted to keep the call site changes small (so that any tests against the function &lt;code&gt;Get-DatabasesToCheck&lt;/code&gt; were testing that function, not the caching logic). The changes in the function itself end up being very minimal:&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# before&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$queryResults&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Invoke-Sqlcmd&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ServerInstance &lt;span class=&quot;token variable&quot;&gt;$serverInstance&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;query &lt;span class=&quot;token variable&quot;&gt;$query&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# after&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$queryResults&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-CachedScriptBlockResult&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Key &lt;span class=&quot;token variable&quot;&gt;$serverInstance&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ScriptBlock &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;Invoke-Sqlcmd&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ServerInstance &lt;span class=&quot;token variable&quot;&gt;$serverInstance&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;query &lt;span class=&quot;token variable&quot;&gt;$query&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;Get-CachedScriptBlockResult&lt;/code&gt; function looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;Function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Get-CachedScriptBlockResult&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token namespace&quot;&gt;[cmdletbinding()]&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;Param&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token namespace&quot;&gt;[Parameter(Mandatory = $true)]&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$Key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token namespace&quot;&gt;[Parameter(Mandatory = $true)]&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token namespace&quot;&gt;[ScriptBlock]&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$ScriptBlock&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$CACHE_VARIABLE_NAME&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;SQLChecks_Cache&quot;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-not&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Get-Variable&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Name &lt;span class=&quot;token variable&quot;&gt;$CACHE_VARIABLE_NAME&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Scope Global &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ErrorAction SilentlyContinue&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;Set-Variable&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Name &lt;span class=&quot;token variable&quot;&gt;$CACHE_VARIABLE_NAME&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Scope Global &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Value @&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$cache&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-Variable&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Name &lt;span class=&quot;token variable&quot;&gt;$CACHE_VARIABLE_NAME&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Scope Global&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-not&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$cache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Value&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ContainsKey&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$Key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$cachedValue&lt;/span&gt; = &amp;amp;&lt;span class=&quot;token variable&quot;&gt;$ScriptBlock&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$cache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Value&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$Key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$cachedValue&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$cachedValue&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$cache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Value&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$Key&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$cachedValue&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that a better name for this function might have been GetOrAdd-ScriptBlockResult - though that isn&#39;t an approved verb, so I went with &#39;Get&#39;. Anyone have a better idea?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We use a global variable that holds a hashtable to act as our cache. The cache key is a string, and the value can be any object. The flow of the function is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Check to see if the global variable exists, if not create it&lt;/li&gt;
&lt;li&gt;Check to see if the cache key exists
&lt;ul&gt;
&lt;li&gt;If it doesn&#39;t exist then store the result of invoking the script block in the key&lt;/li&gt;
&lt;li&gt;If it does exist, return the value in the cache&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;aside-why-scriptblock-vs-objects&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/12/adding-caching-to-your-powershell-scripts/#aside-why-scriptblock-vs-objects&quot;&gt;Aside: Why ScriptBlock vs. Objects?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The reason we use a &lt;code&gt;ScriptBlock&lt;/code&gt; rather than an object is to keep the call site as neat as possible. If we&#39;d have elected to make the cache function take objects as a parameter then the interaction would have looked like this:&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# example of using an object cache instead&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# before&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$queryResults&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Invoke-Sqlcmd&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ServerInstance &lt;span class=&quot;token variable&quot;&gt;$serverInstance&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;query &lt;span class=&quot;token variable&quot;&gt;$query&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# after&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$databases&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-CachedValue&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Key &lt;span class=&quot;token variable&quot;&gt;$serverInstance&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-not&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$databases&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$databases&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Invoke-SqlCmd&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ServerInstance &lt;span class=&quot;token variable&quot;&gt;$serverInstance&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Query &lt;span class=&quot;token variable&quot;&gt;$query&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;Add-CachedValue&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Key &lt;span class=&quot;token variable&quot;&gt;$serverInstance&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Value &lt;span class=&quot;token variable&quot;&gt;$databases&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A tricky edge case here is what if the value cached is &lt;em&gt;supposed&lt;/em&gt; to be null? We could change our &lt;code&gt;Get-CachedValue&lt;/code&gt; function to return a success flag and instead pass a reference to an object we want our cache to populate. This, combined with the fact I wanted to make the caching as easy to add (and not require any logic changes) meant &lt;code&gt;ScriptBlock&lt;/code&gt; was the winner.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Storing null values is why the &lt;code&gt;Get-CachedScriptBlockResult&lt;/code&gt; tests for existence by looking at keys, not seeing if the value exists.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;testing-the-cache&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/12/adding-caching-to-your-powershell-scripts/#testing-the-cache&quot;&gt;Testing the Cache&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To ensure the cache worked as expected I added a few tests (with Pester, of course). Although basic, these did catch some edge cases and helped build the list of caveats. The below tests leverage Pester&#39;s &lt;a href=&quot;https://github.com/pester/Pester/wiki/Mocking-with-Pester&quot;&gt;mocking functionality&lt;/a&gt;, which let&#39;s us test that the &lt;code&gt;ScriptBlock&lt;/code&gt; is invoked only once despite repeated calls.&lt;/p&gt;
&lt;p&gt;You can view the full set of tests (which cover basic functionality and null caching) &lt;a href=&quot;https://github.com/taddison/SQLChecks/blob/master/tests/Get-CachedScriptBlockResult.tests.ps1&quot;&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;Function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Get-ExpensiveToComputeValue&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&quot;Non-mocked&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Describe &lt;span class=&quot;token string&quot;&gt;&quot;Get-CachedScriptBlockResult&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    Context &lt;span class=&quot;token string&quot;&gt;&quot;calls script block the correct number of times &quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        BeforeAll &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token function&quot;&gt;Remove-SQLChecksCache&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        Mock &lt;span class=&quot;token function&quot;&gt;Get-ExpensiveToComputeValue&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;mocked&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;Get-CachedScriptBlockResult&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Key &lt;span class=&quot;token string&quot;&gt;&quot;test&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ScriptBlock &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Get-ExpensiveToComputeValue&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        It &lt;span class=&quot;token string&quot;&gt;&quot;calls the function once to populate the cache&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token function&quot;&gt;Assert-MockCalled&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;CommandName &lt;span class=&quot;token function&quot;&gt;Get-ExpensiveToComputeValue&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Times 1&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;Get-CachedScriptBlockResult&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Key &lt;span class=&quot;token string&quot;&gt;&quot;test&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ScriptBlock &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Get-ExpensiveToComputeValue&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        It &lt;span class=&quot;token string&quot;&gt;&quot;doesn&#39;t call the function when the value is in the cache&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token function&quot;&gt;Assert-MockCalled&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;CommandName &lt;span class=&quot;token function&quot;&gt;Get-ExpensiveToComputeValue&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Exactly &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Times 1&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;conclusion-and-caveats&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/12/adding-caching-to-your-powershell-scripts/#conclusion-and-caveats&quot;&gt;Conclusion and Caveats&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As with any piece of code the most important thing is correctness (and then somewhere behind that come performance and maintainability). If adding caching breaks correctness, you definitely have a problem! If there is no performance problem then avoid caching, as it will often do more harm than good.&lt;/p&gt;
&lt;p&gt;Once you&#39;re sure caching is for you then take some time to understand the &lt;a href=&quot;https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_scopes&quot;&gt;scope of global variables&lt;/a&gt;, and what is sharing your cache variable&#39;s name (perhaps avoid calling it something like &lt;code&gt;$cache&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;There are a few gotchas when it comes to caching and you&#39;ll definitely have issues if you expect caching to work via remoting or in a PowerShell job (serialization of ScriptBlocks won&#39;t work). You will also need to take care with any Pester tests you have that might need the cache resetting between examples (you&#39;ll notice above the call to &lt;code&gt;Remove-SQLChecksCache&lt;/code&gt; - this clears the global variable).&lt;/p&gt;
&lt;p&gt;With all that said - if you do have a good fit for caching the results can be spectacular. After implementing the cache our daily SQLChecks run came down from 20 minutes to less than 60 seconds. That benefit only grows as more tests are added, and so judiciously applied this technique can be a boon.&lt;/p&gt;
&lt;p&gt;One ancillary benefit you get is the ability to inspect certain state (the cache is just a variable, take a look inside and see what your scripts are up to!).&lt;/p&gt;
</description>
      <pubDate>Mon, 31 Dec 2018 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2018/12/adding-caching-to-your-powershell-scripts/</guid>
    </item>
    <item>
      <title>SQL Managed Backups and Operating System Error 87</title>
      <link>https://tjaddison.com/blog/2018/11/sql-managed-backups-and-operating-system-error-87/</link>
      <description>&lt;p&gt;We use &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/relational-databases/backup-restore/sql-server-managed-backup-to-microsoft-azure&quot;&gt;SQL Managed backups&lt;/a&gt; for our on-premises SQL Servers, and have been very impressed with it (from a speed, management, and cost perspective). Shortly after deploying the solution though the SQL error logs started to log errors when attempting to read managed backups:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;BackupIoRequest::ReportIoError: read failure on backup device
&#39;https://allthebackups.blob.core.windows.net/ServerOne/LongFileName.log&#39;.
Operating system error 87(The parameter is incorrect.).
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nothing suggested there were any issues - backups were still being taken, our backup chain wasn&#39;t broken (restores were fine) - but this error was being logged all the time.&lt;/p&gt;
&lt;p&gt;Understanding where the error came from and how to fix it required a better understanding of exactly how managed backup works.&lt;/p&gt;
&lt;h2 id=&quot;how-does-managed-backup-know-what-backups-are-available&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/11/sql-managed-backups-and-operating-system-error-87/#how-does-managed-backup-know-what-backups-are-available&quot;&gt;How does managed backup know what backups are available?&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The backup in question was from an availability group - the replica the backup runs from frequently changes, but managed backup (via msdb) always has a full list of available backups for the database.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This doesn&#39;t appear to be documented anywhere, but we were able (through a combination of reasoning and monitoring) to establish the following set of operations happens to maintain the list of &#39;what backups exist for this database&#39;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Managed backup gets a list of files in the target container&lt;/li&gt;
&lt;li&gt;Managed backup performs a &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/t-sql/statements/restore-statements-headeronly-transact-sql&quot;&gt;restore headeronly&lt;/a&gt; on each file, to get metadata about that file&lt;/li&gt;
&lt;li&gt;Managed backup uses this information to determine what it needs to do (delete old backups, create a new full backup, create a transaction log backup)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;why-do-we-get-operating-system-error-87&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/11/sql-managed-backups-and-operating-system-error-87/#why-do-we-get-operating-system-error-87&quot;&gt;Why do we get operating system error 87?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After downloading the file that was generating the error and attempting to restore it, we realised the backup was corrupt. The file size tipped us off to the fact this was probably a partially complete backup (kilobytes rather than megabytes). We were able to generate our own &#39;corrupt&#39; backups by killing an in-flight backup operation, which generated a partial (and corrupt) backup in blob storage.&lt;/p&gt;
&lt;p&gt;Although managed backup will delete backups that are outside of the retention period, in the case of a corrupt backup it has no idea what database it belongs to, nor how old it is. As such it won&#39;t delete the file, and it&#39;ll sit there in the blob storage container forever.&lt;/p&gt;
&lt;p&gt;The key thing we came to understand is that there is no central on-premises list of &#39;what backups I have taken&#39;, and that each instance/replica is responsible for interrogating Azure storage to establish what files exist.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This behaviour is actually a pretty neat feature - unlike relying on information in msdb for prior backups (which could have been deleted, corrupted, etc.) managed backup actually interrogates the backup target to determine what is there.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;how-do-i-fix-it&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/11/sql-managed-backups-and-operating-system-error-87/#how-do-i-fix-it&quot;&gt;How do I fix it?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The fix is to delete the corrupted backup file from Azure storage. We currently do this manually (as it happens so rarely) but it could be automated to react to the message in the SQL error log. In the future something like &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/storage/common/storage-lifecycle-managment-concepts&quot;&gt;automated storage lifecycle management&lt;/a&gt; would allow us to set a policy to automatically delete blobs that exceed our maximum retention period, meaning this error would auto-heal after the retention period.&lt;/p&gt;
&lt;p&gt;The root cause of the corrupt backup for us was a (planned!) failover. It&#39;s also feasible a network blip could terminate an in-progress backup, or perhaps good old-fashioned storage corruption (which is something we really would care about, especially if every backup became corrupt).&lt;/p&gt;
&lt;p&gt;Outside of some rather sparse documentation (if we&#39;d known how managed backup worked we&#39;d have figured this out a lot faster) managed backup is something we&#39;ve been really impressed with - so if you&#39;re hitting this scenario you now know how to fix it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A big thanks to my colleague Jose for getting to the bottom of this one&lt;/p&gt;
&lt;/blockquote&gt;
</description>
      <pubDate>Fri, 16 Nov 2018 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2018/11/sql-managed-backups-and-operating-system-error-87/</guid>
    </item>
    <item>
      <title>Resumable Online Index Rebuilds - SQL 2017&#39;s Silver Bullet</title>
      <link>https://tjaddison.com/blog/2018/11/resumable-online-index-rebuilds-sql2017s-silver-bullet/</link>
      <description>&lt;p&gt;Every new version of SQL Server comes with a whole grab-bag of new features that open up exciting possibilities, or obviate the need for workarounds (or dirty hacks). In the run-up to deploying SQL 2017 I thought that &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/relational-databases/automatic-tuning/automatic-tuning#automatic-plan-correction&quot;&gt;automatic plan correction&lt;/a&gt; was going to be the closest thing to a silver bullet I&#39;d seen in any release so far (in terms of value added vs. effort to implement), but it has been eclipsed for us by the &lt;em&gt;awesomeness&lt;/em&gt; of Resumable Online Index Rebuilds (ROIR).&lt;/p&gt;
&lt;p&gt;In this post I&#39;ll talk through a few of the scenarios where this feature really shines, and how it has transformed the way we think about index maintenance. If you&#39;d like more details about how ROIR is implemented I&#39;d encourage you to read through the &lt;a href=&quot;http://www.vldb.org/pvldb/vol10/p1742-antonopoulos.pdf&quot;&gt;excellent paper detailing ROIR&lt;/a&gt; - this covers how the online index rebuild algorithm was updated, and also demonstrates how in most cases ROIR outperforms the non-resumable version in terms of performance.&lt;/p&gt;
&lt;h2 id=&quot;when-index-rebuilds-are-kind-of-a-big-deal&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/11/resumable-online-index-rebuilds-sql2017s-silver-bullet/#when-index-rebuilds-are-kind-of-a-big-deal&quot;&gt;When index rebuilds are kind of a big deal&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A few conditions (either individually or in combination) can make index rebuilds a pretty big deal in an environment:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;24/7 operations (no such thing as a &#39;quiet time&#39; or maintenance window)&lt;/li&gt;
&lt;li&gt;Index is very large (takes a long time to rebuild)&lt;/li&gt;
&lt;li&gt;Transaction volume is high (log truncation being blocked means log utilisation grows rapidly)&lt;/li&gt;
&lt;li&gt;Presence of Availability Group (AG) replicas (index build has to be replayed on 1...many servers, potentially getting blocked behind queries)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Having enterprise edition (online rebuilds) and fast local-attached drives (NVMe and availability groups) help with the 24/7 and speed issues, but transaction log volume and AG replicas remain a challenge. In the worst case you end up needing a huge transaction log on your primary replica to support the rebuild itself, and then it can grow even larger if redo gets blocked on the secondary replica.&lt;/p&gt;
&lt;p&gt;Those last two bullet points caused us to go through a pretty rigorous scheduling process for any maintenance on indexes larger than 100GB in one of our high-volume databases. What should have been an automated script turned into an affair which needed attention from the DB and App teams, potentially dialling down other operations to reduce load (not to mention the DBA monitoring the log utilisation and invariably sweating bullets as the rebuild chugs along). There may have also been several reported incidents of reporting queries on replicas being killed if they got in front of a &lt;code&gt;SCH-M&lt;/code&gt; lock.&lt;/p&gt;
&lt;p&gt;Not ideal, and that isn&#39;t even the worst of it.&lt;/p&gt;
&lt;h2 id=&quot;throwing-away-good-work&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/11/resumable-online-index-rebuilds-sql2017s-silver-bullet/#throwing-away-good-work&quot;&gt;Throwing away good work&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;On our mission critical instances we have no tolerance for any kind of blocking on critical tables. As such we take an extremely paranoid approach to any index of rebuild:&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; lock_timeout &lt;span class=&quot;token number&quot;&gt;1500&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;alter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;index&lt;/span&gt; IX_ImportantIndex &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ImportantTable rebuild &lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;online &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Online index rebuilds aren&#39;t entirely lock-free - there is a &lt;code&gt;SCH-M&lt;/code&gt; lock taken at the start and end of the process, and although &lt;a href=&quot;https://blogs.msdn.microsoft.com/sql_shep/2014/04/30/sql-server-2014-managed-lock-priority-for-partition-switch-and-online-reindex/&quot;&gt;managed lock priority&lt;/a&gt; allows you to wait at low priority the options for what to do if your session &lt;em&gt;can&#39;t&lt;/em&gt; get the lock after the timeout are pretty limited:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kill yourself&lt;/li&gt;
&lt;li&gt;Kill anything blocking you&lt;/li&gt;
&lt;li&gt;Elevate to regular lock priority&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The second two aren&#39;t really options - critical work is either going to be killed, or get blocked (for who knows how long - option 3 could potentially wait forever).&lt;/p&gt;
&lt;p&gt;The first option is the only one we&#39;d really entertain, but in our testing we found that on a busy OLTP system waiting at low priority for even five minutes had less chance of getting through than waiting at normal priority for 1.5 seconds. This is obviously specific to our environment, but it is what we ended up going with. This sometimes meant that a 30 minute index rebuild could end up getting rolled back because it couldn&#39;t acquire a brief &lt;code&gt;SCH-M&lt;/code&gt; lock.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The option of leaving the transaction open and waiting e.g. 1 hour at low priority was problematic due to the log usage. We were never entirely happy this index maintenance setup, and it was relegated to the pile of &#39;works but not ideal, let&#39;s hope we don&#39;t have to rebuild critical indexes that often&#39;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;enter-the-silver-bullet-roir&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/11/resumable-online-index-rebuilds-sql2017s-silver-bullet/#enter-the-silver-bullet-roir&quot;&gt;Enter the silver bullet - ROIR&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In order to support resumable index operations in SQL 2017 the rebuild algorithm was changed to support batching. Internally SQL Server will operate on batches of rows, committing work every 100k rows and allowing the transaction log to truncate after each batch. In the event that the rebuild is stopped (either by a pause command, or something like a network blip/failover) the maximum amount of work that is &#39;lost&#39; will be a single batch.&lt;/p&gt;
&lt;p&gt;The icing on the cake is that resumable index rebuilds will typically execute &lt;em&gt;faster&lt;/em&gt; than the regular, non-resumable variety. With a small change our index maintenance suddenly becomes almost risk-free:&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; lock_timeout &lt;span class=&quot;token number&quot;&gt;1500&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;alter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;index&lt;/span&gt; IX_ImportantIndex &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ImportantTable rebuild &lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;online &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; resumable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the rebuild gets to 100% complete and attempts to perform the switch (acquiring the &lt;code&gt;SCH-M&lt;/code&gt; lock) and fails, we haven&#39;t lost all the work. Looking in &lt;code&gt;sys.index_resumable_operations&lt;/code&gt; we&#39;ll see our rebuild at 100%.&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; sys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;index_resumable_operations&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/11/resumable-online-index-rebuilds-sql2017s-silver-bullet/HY8QO1QBpq-295.avif 295w, https://tjaddison.com/blog/2018/11/resumable-online-index-rebuilds-sql2017s-silver-bullet/HY8QO1QBpq-590.avif 590w, https://tjaddison.com/blog/2018/11/resumable-online-index-rebuilds-sql2017s-silver-bullet/HY8QO1QBpq-885.avif 885w, https://tjaddison.com/blog/2018/11/resumable-online-index-rebuilds-sql2017s-silver-bullet/HY8QO1QBpq-1180.avif 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/11/resumable-online-index-rebuilds-sql2017s-silver-bullet/HY8QO1QBpq-295.webp 295w, https://tjaddison.com/blog/2018/11/resumable-online-index-rebuilds-sql2017s-silver-bullet/HY8QO1QBpq-590.webp 590w, https://tjaddison.com/blog/2018/11/resumable-online-index-rebuilds-sql2017s-silver-bullet/HY8QO1QBpq-885.webp 885w, https://tjaddison.com/blog/2018/11/resumable-online-index-rebuilds-sql2017s-silver-bullet/HY8QO1QBpq-1180.webp 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2018/11/resumable-online-index-rebuilds-sql2017s-silver-bullet/HY8QO1QBpq-295.jpeg 295w, https://tjaddison.com/blog/2018/11/resumable-online-index-rebuilds-sql2017s-silver-bullet/HY8QO1QBpq-590.jpeg 590w, https://tjaddison.com/blog/2018/11/resumable-online-index-rebuilds-sql2017s-silver-bullet/HY8QO1QBpq-885.jpeg 885w, https://tjaddison.com/blog/2018/11/resumable-online-index-rebuilds-sql2017s-silver-bullet/HY8QO1QBpq-1180.jpeg 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Paused index rebuild&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/11/resumable-online-index-rebuilds-sql2017s-silver-bullet/HY8QO1QBpq-295.jpeg&quot; width=&quot;1180&quot; height=&quot;85&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;The following SQL attempts to complete the rebuild - sitting at 100% the only thing it needs to do is take the schema stability lock to switch out the new index for the old. As we&#39;re using a lock timeout of 1500ms it might take a few tries, but crucially we do not have to start the index rebuild from scratch.&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;alter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;index&lt;/span&gt; IX_ImportantIndex &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ImportantTable resume&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;resumable-index-rebuild-recommendations&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/11/resumable-online-index-rebuilds-sql2017s-silver-bullet/#resumable-index-rebuild-recommendations&quot;&gt;Resumable index rebuild recommendations&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First of all - use this everywhere. There are almost no situations in which you don&#39;t want to use resumable index rebuilds (this will be easier in SQL 2019 when you can set the options &lt;code&gt;ELEVATE_ONLINE&lt;/code&gt; and &lt;code&gt;ELEVATE_RESUMABLE&lt;/code&gt;). Even outside of an AG or high-volume scenario, why would you want to block log truncation, or put yourself at risk of losing the rebuild because your network drops (or because SSMS crashed...).&lt;/p&gt;
&lt;p&gt;The new risk that resumable rebuilds bring is that you&#39;ll end up with a database full of paused rebuilds, all of which are using up additional space and adding overhead to all the DML against their target indexes. I&#39;d recommend at minimum an agent job that looks for any paused indexes which were paused more than N hours ago and aborts them.&lt;/p&gt;
&lt;p&gt;In our environment we&#39;re currently exploring a solution that gives us much finer grained control over index maintenance by leveraging the &lt;code&gt;MAX_DURATION&lt;/code&gt; option. This allows us to tell the rebuild to abort after N minutes, and via an agent job we can optionally choose to resume the rebuild, wait a while, or abort it. We&#39;re hoping to use this to better leverage available CPU/IO capacity across the cluster and ramp up/down operations with capacity. Did I mention that you can resume operations with a different &lt;code&gt;MAXDOP&lt;/code&gt;?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It&#39;s still early days for our experimentation, but the ability to dial index rebuilds up/down depending on how the cluster reacts to the current &lt;code&gt;MAXDOP&lt;/code&gt; is almost as awesome as the ROIR feature itself.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
      <pubDate>Sat, 03 Nov 2018 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2018/11/resumable-online-index-rebuilds-sql2017s-silver-bullet/</guid>
    </item>
    <item>
      <title>Mitigating SERVICE_BROKER_WAITFOR_MANAGER latch waits</title>
      <link>https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/</link>
      <description>&lt;p&gt;Our production environment recently started generating alerts on huge blocking chains (100s of requests), which were accompanied by increased database response times for various procedures. The blocking chains all had the latch wait &lt;code&gt;SERVICE_BROKER_WAITFOR_MANAGER&lt;/code&gt; in common, some of which were blocking for seconds (adding significant overhead to operations that would normally complete in a few milliseconds).&lt;/p&gt;
&lt;p&gt;In this post I&#39;ll walk through what an environment leveraging service broker might look like, show you how to reproduce the issue, and offer some mitigation strategies/general advice for service broker at scale.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/lnvvXUIZus-295.avif 295w, https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/lnvvXUIZus-590.avif 590w, https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/lnvvXUIZus-885.avif 885w, https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/lnvvXUIZus-1180.avif 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/lnvvXUIZus-295.webp 295w, https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/lnvvXUIZus-590.webp 590w, https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/lnvvXUIZus-885.webp 885w, https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/lnvvXUIZus-1180.webp 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/lnvvXUIZus-295.jpeg 295w, https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/lnvvXUIZus-590.jpeg 590w, https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/lnvvXUIZus-885.jpeg 885w, https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/lnvvXUIZus-1180.jpeg 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Blocking&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/lnvvXUIZus-295.jpeg&quot; width=&quot;1180&quot; height=&quot;409&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h2 id=&quot;service-broker-recap&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/#service-broker-recap&quot;&gt;Service Broker Recap&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;SQL Service Broker (SSB) can be used as a messaging system to decouple applications. Note that SSB is a complex product and this specific example only covers one way it can be used (or perhaps misused).&lt;/p&gt;
&lt;p&gt;Imagine a website application wants to process a card payment - the website would queue a &lt;code&gt;process_payment&lt;/code&gt; message (to a SSB queue). That SSB queue is monitored by a separate payment application. The payment application would receive the &lt;code&gt;process_payment&lt;/code&gt; message, process the payment, and respond to the website with the result. This is an example of a dialog (two-way) conversation.&lt;/p&gt;
&lt;p&gt;SSB also supports fire and forget (care needs to be taken to ensure it is &lt;a href=&quot;http://rusanu.com/2006/04/06/fire-and-forget-good-for-the-military-but-not-for-service-broker-conversations/&quot;&gt;implemented correctly&lt;/a&gt;), which you might use if the website wants to send an email, but doesn&#39;t need to wait for the result (the website puts an &lt;code&gt;order_confirm_email&lt;/code&gt; message on a SSB queue, and the email application processes it from that queue at some point in the future). Background processes can also take advantage of fire and forget to queue up a large amount of work - imagine an email for &lt;code&gt;newsletter_mail&lt;/code&gt; for every subscriber.&lt;/p&gt;
&lt;p&gt;In a complex application there can be dozens or hundreds of different messages and queues, and multiple applications queueing/dequeueing messaging. Scaling SSB to cope with high throughput is a challenge of it&#39;s own (see this &lt;a href=&quot;https://docs.microsoft.com/en-us/previous-versions/sql/sql-server-2008/dd576261(v=sql.100)&quot;&gt;whitepaper on scaling SSB from 2009&lt;/a&gt;) - the specific scaling challenge I&#39;ll discuss in this post relates to waiters (applications waiting to dequeue messages from a queue).&lt;/p&gt;
&lt;p&gt;Each queue can have 0 to N requests waiting to process (dequeue) a message. While you could have applications check each queue periodically (polling with &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/t-sql/statements/receive-transact-sql&quot;&gt;receive&lt;/a&gt;), SSB allows you to register and &#39;wait&#39; on a queue, such that the request will block until a message arrives, at which point it will be returned to the calling application. You do this with the &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/t-sql/language-elements/waitfor-transact-sql&quot;&gt;waitfor&lt;/a&gt; keyword. In our environment we use &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/t-sql/language-elements/waitfor-transact-sql&quot;&gt;waitfor&lt;/a&gt; to ensure each queue has a minimum of 1 waiter at all times.&lt;/p&gt;
&lt;p&gt;The final bit of background needed is that you can use &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/relational-databases/service-broker/event-notifications&quot;&gt;event notifications&lt;/a&gt; to have SQL Server notify you when there are messages waiting to be processed on a queue (specifically with the &lt;code&gt;QUEUE_ACTIVATION&lt;/code&gt; event). We use this to create additional threads (which call the queue with &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/t-sql/language-elements/waitfor-transact-sql&quot;&gt;waitfor&lt;/a&gt;) to process multiple messages concurrently.&lt;/p&gt;
&lt;p&gt;In summary:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One queue per decoupled process&lt;/li&gt;
&lt;li&gt;At least one reader per-queue&lt;/li&gt;
&lt;li&gt;The number of readers per-queue will scale as there are more messages to process&lt;/li&gt;
&lt;li&gt;The way readers interact with the queue is via the &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/t-sql/language-elements/waitfor-transact-sql&quot;&gt;waitfor&lt;/a&gt; keyword&lt;/li&gt;
&lt;li&gt;This pattern is used for both dialogs (two-way) and fire and forget (one-way) messaging&lt;/li&gt;
&lt;li&gt;Dialogs are typically latency sensitive (we care about the response time)&lt;/li&gt;
&lt;li&gt;Fire and forget are typically latency insensitive, and can be very high volume&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-problem&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/#the-problem&quot;&gt;The problem&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It starts with alerts for blocking in production. The environment in question normally never has anything but transient (&amp;lt;100ms) blocking, so to see an alert go off with a few seconds of blocking involving hundreds of requests was...disconcerting! The blocking alert helpfully pointed the finger at service broker (&lt;code&gt;SERVICE_BROKER_WAITFOR_MANAGER&lt;/code&gt;), though after a quick Google we assumed we were on our own (when the &lt;a href=&quot;https://www.sqlskills.com/help/latches/service_broker_waitfor_manager/&quot;&gt;latch waits library&lt;/a&gt; says TBD, typically you&#39;re in trouble).&lt;/p&gt;
&lt;p&gt;In this case however the &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-os-latch-stats-transact-sql&quot;&gt;Microsoft latch docs&lt;/a&gt; had us covered. Most of the time &#39;Internal use only&#39; is all you get, but in this case case the description gave us a few concrete avenues to investigate:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Used to synchronize an instance level map of waiter queues. One queue exists per database ID, Database Version, and Queue ID tuple. Contention on latches of this class can occur when many connections are: In a WAITFOR(RECEIVE) wait state; calling WAITFOR(RECEIVE); exceeding the WAITFOR timeout; receiving a message; committing or rolling back the transaction that contains the WAITFOR(RECEIVE); You can reduce the contention by reducing the number of threads in a WAITFOR(RECEIVE) wait state.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Armed with this, and after checking to see if this was actually leading to increases in response time for latency-sensitive calls (it was), I set out to try and isolate and remedy the waits.&lt;/p&gt;
&lt;h2 id=&quot;reproduction&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/#reproduction&quot;&gt;Reproduction&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;setup&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/#setup&quot;&gt;Setup&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To reproduce the issue all we need is a single queue, and a way of generating concurrent activity against that queue. Note that This does not constitute what a real deployment of SSB looks like (with multiple queues, request/response services, priorities, etc.).&lt;/p&gt;
&lt;p&gt;In order to generate load against the queues I used &lt;a href=&quot;https://github.com/taddison/SQLDriver&quot;&gt;SQLDriver&lt;/a&gt; - you can download the &lt;a href=&quot;https://github.com/taddison/SQLDriver/releases/tag/1.0&quot;&gt;SQLDriver 1.0&lt;/a&gt; standalone binaries, or use any other stress testing tool. Crucially, you want to ensure your tool will tell you about the response times and not just the total runtime.&lt;/p&gt;
&lt;p&gt;The database and schema look like this:&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;database&lt;/span&gt; BrokerTest&lt;br /&gt;go&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;use&lt;/span&gt; BrokerTest&lt;br /&gt;go&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;alter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;database&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; enable_broker&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; master &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; encryption &lt;span class=&quot;token keyword&quot;&gt;by&lt;/span&gt; password &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;secure&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;go&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;alter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;database&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; compatibility_level &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;140&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;alter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;database&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; recovery &lt;span class=&quot;token keyword&quot;&gt;simple&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;alter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;database&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;current&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; delayed_durability &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; forced&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;go&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; queue dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TestQueue&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; queue dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TestQueue1&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The reason we set delayed durability to forced is to minimise/eliminate any &lt;code&gt;WRITELOG&lt;/code&gt; waits. Although it doesn&#39;t appear to be documented anywhere, calling &lt;code&gt;waitfor(receive...)&lt;/code&gt; will generate transaction log - even when called on a totally empty queue.&lt;/p&gt;
&lt;p&gt;To start with I ran a load test and looked at &lt;code&gt;sp_whoisactive&lt;/code&gt; to confirm the wait was showing up. The following command will run 100 concurrent waitfor-receives, each with a timeout of 1ms. Each thread will execute the statement 10000 times.&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;SqlDriver.exe&lt;br /&gt;  &lt;span class=&quot;token parameter variable&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;waitfor(receive top(1) * from dbo.TestQueue), timeout 1&quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token parameter variable&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token parameter variable&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;server=localhost;initial catalog=BrokerTest;integrated security=SSPI;Application Name=&#39;SQLDriver&#39;;max pool size=1024&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/wLGrTT6Urs-295.avif 295w, https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/wLGrTT6Urs-590.avif 590w, https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/wLGrTT6Urs-885.avif 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/wLGrTT6Urs-295.webp 295w, https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/wLGrTT6Urs-590.webp 590w, https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/wLGrTT6Urs-885.webp 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/wLGrTT6Urs-295.jpeg 295w, https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/wLGrTT6Urs-590.jpeg 590w, https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/wLGrTT6Urs-885.jpeg 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Initial load test results&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/wLGrTT6Urs-295.jpeg&quot; width=&quot;885&quot; height=&quot;253&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;The numbers that I&#39;ll focus on for the rest of the post are the median response time (P50), and the tail latency (P95). In the above example the median response time was &lt;strong&gt;9ms&lt;/strong&gt;, and the 95th percentile response time was &lt;strong&gt;16ms&lt;/strong&gt;. In a typical system the timeout would be much higher than 1ms (at such a low timeout we might as well be polling the server), and even with a single thread we&#39;d see variation with timeouts &amp;lt; 10ms - though most of the wait time would be the benign wait &lt;code&gt;BROKER_RECEIVE_WAITFOR&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Eyeballing the waits for this first test I did see the expected latch waits (both LATCH_SH and LATCH_EX for &lt;code&gt;SERVICE_BROKER_WAITFOR_MANAGER&lt;/code&gt;), though I also saw the wait type &lt;code&gt;WAITFOR_PER_QUEUE&lt;/code&gt;. This wait type is &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-os-wait-stats-transact-sql&quot;&gt;not documented&lt;/a&gt;, and seems to indicate there is a maximum number of waiters per queue. In additional testing I got up to 700 concurrent waiters on one queue (before my laptop ran out of worker threads), so this wait appears to be linked with registering a waitfor, rather than the total number of waiters.&lt;/p&gt;
&lt;h3 id=&quot;load-testing-single-queue&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/#load-testing-single-queue&quot;&gt;Load testing - single queue&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To ensure there were no other waits being missed by eyeballing &lt;code&gt;sp_whoisactive&lt;/code&gt;, I used an extended event trace for all waits coming from the &lt;a href=&quot;https://github.com/taddison/SQLDriver&quot;&gt;SQLDriver&lt;/a&gt; application.&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;exists&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; sys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;server_event_sessions &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;SQLDriverLatches&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;drop&lt;/span&gt; event &lt;span class=&quot;token keyword&quot;&gt;session&lt;/span&gt; SQLDriverLatches &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; server&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;go&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; event &lt;span class=&quot;token keyword&quot;&gt;session&lt;/span&gt; SQLDriverLatches &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; server&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;add&lt;/span&gt; event sqlos&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;wait_info &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; sqlserver&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;client_app_name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; N&lt;span class=&quot;token string&quot;&gt;&#39;SQLDriver&#39;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt;   opcode &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* End - otherwise we get the start &amp;amp; end of every wait */&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt;   duration &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;add&lt;/span&gt; target package0&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;asynchronous_file_target &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; filename &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; N&lt;span class=&quot;token string&quot;&gt;&#39;C:&#92;temp&#92;SQLDriverLatches.xel&#39;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  max_dispatch_latency &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; seconds&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;go&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;alter&lt;/span&gt; event &lt;span class=&quot;token keyword&quot;&gt;session&lt;/span&gt; SQLDriverWaits &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; server state &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;/* Run the load test! */&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;alter&lt;/span&gt; event &lt;span class=&quot;token keyword&quot;&gt;session&lt;/span&gt; SQLDriverWaits &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; server state &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; stop&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Creation of the extended event session and the analysis heavily borrows from Paul Randall&#39;s post on &lt;a href=&quot;http://www.sqlskills.com/blogs/paul/capturing-wait-stats-for-a-single-operation/&quot;&gt;session level wait stats&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The results can be analysed with the following SQL - the &lt;code&gt;.xel&lt;/code&gt; files should be deleted between each test. Note that this query can take a &lt;em&gt;long time&lt;/em&gt; if there are a large number of waits to analyse.&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;drop&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;exists&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;#xetemp;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; cast&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event_data &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; xml&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; EventData&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;into&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;#xetemp&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; sys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fn_xe_file_target_read_file &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;C:&#92;temp&#92;SQLDriverWaits*.xel&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt;  dat&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;WaitType&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; WaitCount&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dat&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Duration&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; WaitTime&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;coalesce&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dat&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SignalDuration&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; SignalWaitTime&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;#xetemp as x&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;cross&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;apply&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;EventData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;(/event/data[@name=&#39;&#39;wait_type&#39;&#39;]/text)[1]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;varchar(100)&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; WaitType&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;EventData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;(/event/data[@name=&#39;&#39;duration&#39;&#39;])[1]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;bigint&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; Duration&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;EventData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;(/event/data[@name=&#39;&#39;signal_duration&#39;&#39;])[1]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;bigint&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; SignalDuration&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; dat&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;by&lt;/span&gt; dat&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;WaitType&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;desc&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The extended event trace only tells us there is a latch wait (LATCH_EX/LATCH_SH), but not the specific latch being waited on. To track that, I cleared the latch stats DMV before each load test, and then grabbed the results after:&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;dbcc&lt;/span&gt; sqlperf&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;sys.dm_os_latch_stats&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; clear&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;/* Run the load test! */&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; sys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dm_os_latch_stats&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;by&lt;/span&gt; wait_time_ms &lt;span class=&quot;token keyword&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I ran benchmarks for 1, 10, 100, 200, and 300 threads - each with 1000 repetitions of a waitfor-receive that has a 10 millisecond timeout. In the below table, SWM Average refers to the average amount of time waiting on the &lt;code&gt;SERVICE_BROKER_WAITFOR_MANAGER&lt;/code&gt; latch - computed from the DMV as &lt;code&gt;wait_time_ms / waiting_requests_count&lt;/code&gt;. Note a thread can acquire latches multiple times during each request, the averages below represent per-latch request attempt.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Threads&lt;/th&gt;
&lt;th&gt;50P&lt;/th&gt;
&lt;th&gt;95P&lt;/th&gt;
&lt;th&gt;SWM Average&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;15ms&lt;/td&gt;
&lt;td&gt;15ms&lt;/td&gt;
&lt;td&gt;0ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;15ms&lt;/td&gt;
&lt;td&gt;16ms&lt;/td&gt;
&lt;td&gt;0.36ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;22ms&lt;/td&gt;
&lt;td&gt;33ms&lt;/td&gt;
&lt;td&gt;3.59ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;32ms&lt;/td&gt;
&lt;td&gt;41ms&lt;/td&gt;
&lt;td&gt;5.76ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;51ms&lt;/td&gt;
&lt;td&gt;64ms&lt;/td&gt;
&lt;td&gt;9.44ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Querying for wait type mainly served to confirm the dominant waits are for latches (plus a smattering of the rogue &lt;code&gt;WAITFOR_PER_QUEUE&lt;/code&gt;). Examining average latch time shows how this wait type increases along with response time, both median and tail latency.&lt;/p&gt;
&lt;p&gt;A high thread count against a single queue will bottleneck on this latch - but what impact does that have if we introduce a second queue?&lt;/p&gt;
&lt;h3 id=&quot;load-testing-second-queue&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/#load-testing-second-queue&quot;&gt;Load testing - second queue&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A common pattern we saw in the blocking alerts was high-volume queues (many messages, and lots of concurrent threads) appeared to be impacting the performance of low-volume queues.&lt;/p&gt;
&lt;p&gt;To reproduce this behaviour I started 100 threads with a high repetition count against &lt;code&gt;dbo.TestQueue&lt;/code&gt;, and then load tested with low thread count against &lt;code&gt;dbo.TestQueue1&lt;/code&gt; with a second instance of &lt;a href=&quot;https://github.com/taddison/SQLDriver&quot;&gt;SQLDriver&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For this benchmark we&#39;re not able to use the latch stats DMV (as the activity against the first queue will dominate), so we&#39;ll look at the response times only.&lt;/p&gt;
&lt;p&gt;To capture wait stats related to the second queue only we modify the application name property of the &#39;background&#39; connection string:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;SqlDriver.exe&lt;br /&gt;  &lt;span class=&quot;token parameter variable&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;waitfor(receive top(1) * from dbo.TestQueue), timeout 10&quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token parameter variable&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10000000&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token parameter variable&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;server=localhost;initial catalog=BrokerTest;integrated security=SSPI;Application Name=&#39;SQLDriverBackground&#39;;max pool size=1024&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The requests against the second queue (from a single thread) have a median response time of &lt;strong&gt;15ms&lt;/strong&gt;, and 95th percentile response time of &lt;strong&gt;27ms&lt;/strong&gt;. The main waits logged are LATCH_EX and LATCH_SH again, and the latch stats DMV confirms no new latches have shown up.&lt;/p&gt;
&lt;p&gt;The second queue is impacted by activity against the first queue - this makes sense as the latch is instance-wide.&lt;/p&gt;
&lt;h3 id=&quot;load-testing-summary&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/#load-testing-summary&quot;&gt;Load testing - summary&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The latency to acquire the &lt;code&gt;SERVICE_BROKER_WAITFOR_MANAGER&lt;/code&gt; latch increases as the number of total concurrent waitfor-receive registrations increases, regardless of the queue.&lt;/p&gt;
&lt;p&gt;As an added bonus, there is a mystery wait (&lt;code&gt;WAITFOR_PER_QUEUE&lt;/code&gt;) you will run into with a high number of waitfor-receives attempting to register concurrently against a single queue.&lt;/p&gt;
&lt;p&gt;So what can we do about it?&lt;/p&gt;
&lt;h2 id=&quot;mitigating-the-problem&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/#mitigating-the-problem&quot;&gt;Mitigating the problem&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The good news is there is a relatively easy workaround - don&#39;t wait(for)!&lt;/p&gt;
&lt;p&gt;Repeating the previous load test but changing the second queue to be &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/t-sql/statements/receive-transact-sql&quot;&gt;receive&lt;/a&gt; (not waitfor-receive) gives a median response time of &lt;strong&gt;0ms&lt;/strong&gt;, 95th percentile response time of &lt;strong&gt;0ms&lt;/strong&gt; - and no waits.&lt;/p&gt;
&lt;p&gt;This is unlikely to be a change you&#39;ll want to make to your application directly, as you&#39;ll now be doing 1000 RPS per thread against the server for every queue being received from (assuming a latency of ~1ms).&lt;/p&gt;
&lt;p&gt;Instead, we can deploy a receive with a sleep, with the sleep acting as our upper bound for response time. In the worst case you start sleeping right after a message arrives, so you&#39;ll wait the sleep duration and then when the next request executes the message will be dequeued.&lt;/p&gt;
&lt;p&gt;The SQL for a receive with sleep might look something like this:&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;receive &lt;span class=&quot;token keyword&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        conversation_handle&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        cast&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message_body &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; xml&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        message_type_name&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt;  dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TestQueue&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; @&lt;span class=&quot;token variable&quot;&gt;@rowcount&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;waitfor&lt;/span&gt; delay &lt;span class=&quot;token string&quot;&gt;&#39;00:00:01&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;other-observations&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/#other-observations&quot;&gt;Other observations&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Having hundreds of threads sitting in a waitfor-receive (with high timeout values) had no noticeable impact on new threads registering a waitfor-receive, either on the same or different queues.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-os-latch-stats-transact-sql&quot;&gt;Microsoft latch docs&lt;/a&gt; indicate this latch only comes into play when you&#39;re modifying the list of waitfors, and so on busy systems where there is lots of concurrent dequeue activity I&#39;d expect similar bottlenecks to manifest (dequeueing a message into an existing waitfor also needs to modify the waitfor list, which means it needs the latch).&lt;/p&gt;
&lt;h2 id=&quot;general-advice&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/#general-advice&quot;&gt;General Advice&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This problem is a great example of something that more hardware can&#39;t fix - no amount of additional cores will help alleviate an instance-wide latch bottleneck. One of the most interesting parts of scaling up a SQL Server is all the wonderful new bottlenecks you discover - higher core counts (and workloads to drive those cores) make it easier to find concurrency bottlenecks (at lower core counts worker thread starvation will probably get you before you hit this latch).&lt;/p&gt;
&lt;p&gt;If you are currently using SSB and think you may end up with hundreds of threads performing operations against SSB queues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For low latency sensitivity (don&#39;t care about response time) - use receive + sleep&lt;/li&gt;
&lt;li&gt;At low volume and high latency sensitivity - use waitfor-receive&lt;/li&gt;
&lt;li&gt;If you need high volume and you have high latency sensitivity...look elsewhere&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In many cases the scale and performance you need to achieve might require you to architect away from SSB:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Perfectly tuned, SSB cannot meet your requirements&lt;/li&gt;
&lt;li&gt;You don&#39;t have (or want to build) the expertise needed to tune SSB&lt;/li&gt;
&lt;li&gt;Other solutions give comparable or superior performance, the cost to migrate is low&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are especially true when you don&#39;t need some of the unique features SSB provides (multicast messaging, routing between multiple databases, poison message handling...). Those features come with a performance price!&lt;/p&gt;
&lt;p&gt;The story of how we found ourselves with so much SSB I think is fairly typical. The pattern we adopted was a victim of it&#39;s own success, and so went from being used in a few places to being used everywhere. Only recently have these bottlenecks started to show up, and a gradual shift to other solutions is letting us break through successive scale barriers.&lt;/p&gt;
&lt;p&gt;Some of those solutions involve switching to dedicated message queue product (e.g. RabbitMQ), and some have involved moving away from queues entirely (e.g. GRPC). Some of the more successful (measured as low implementation cost/high performance gain) have been switching queues out for tables (either regular or in-memory).&lt;/p&gt;
</description>
      <pubDate>Mon, 15 Oct 2018 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2018/10/mitigating-service_broker_waitfor_manager-latch-waits/</guid>
    </item>
    <item>
      <title>Saving and reporting on Pester Infrastructure test results in Log Analytics</title>
      <link>https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/</link>
      <description>&lt;p&gt;&lt;a href=&quot;https://github.com/pester/Pester&quot;&gt;Pester&lt;/a&gt; is a fantastic tool to test and monitor the status of your infrastructure. There are several libraries which leverage this directly (e.g. &lt;a href=&quot;https://github.com/taddison/SQLChecks&quot;&gt;SQLChecks&lt;/a&gt;, &lt;a href=&quot;https://github.com/sqlcollaborative/dbachecks&quot;&gt;DBAChecks&lt;/a&gt;, &lt;a href=&quot;https://github.com/PowerShell/Operation-Validation-Framework&quot;&gt;Operation Validation Framework&lt;/a&gt;), and a growing set of resources discussing how to leverage Pester in this fashion (including a &lt;a href=&quot;https://www.pluralsight.com/courses/pester-infrastructure-testing&quot;&gt;Pluralsight course&lt;/a&gt; and a chapter in the &lt;a href=&quot;https://leanpub.com/pesterbook&quot;&gt;Pester book&lt;/a&gt;). If you don&#39;t already have some tests you can run interactively (is my environment correct &lt;em&gt;right now?&lt;/em&gt;) I&#39;d suggest you start there first.&lt;/p&gt;
&lt;p&gt;Once you&#39;ve got your infrastructure tests running interactively you&#39;ll probably want to start automating them (is my environment correct &lt;em&gt;every day?&lt;/em&gt;). And then at some point you&#39;ll probably find yourself asking questions like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How long has this test been failing for?&lt;/li&gt;
&lt;li&gt;How long does this test normally take to run?&lt;/li&gt;
&lt;li&gt;When was the last time this test failed?&lt;/li&gt;
&lt;li&gt;How is the health of my estate trending over time?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once you&#39;ve invested time into building out a library of Pester &lt;em&gt;tests&lt;/em&gt; for your infrastructure, what you really want to do is analyse the Pester test &lt;em&gt;results&lt;/em&gt;. There are various examples out there that discuss how to persist results to files, XML, SQL databases - but none of these options have the advantages that shipping to Log Analytics provides - which is what we&#39;ll discuss today. A few reasons why I think think sending your results to Log Analytics is the superior choice:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Extremely powerful query language (KQL)&lt;/li&gt;
&lt;li&gt;Can be queried &amp;amp; consumed by a web browser, Power BI, REST API, Flow, Azure Monitor (for alerting)&lt;/li&gt;
&lt;li&gt;Lives with the rest of your infrastructure logs (if you use the machine agents/Azure connectors/etc.)&lt;/li&gt;
&lt;li&gt;Simplified security story (everywhere you run a tests posts to an HTTP endpoint - no fileshares/SQL databases/etc.)&lt;/li&gt;
&lt;li&gt;No impact on the systems under test (how many DBAs have spent time performance tuning their monitoring databases :)?)&lt;/li&gt;
&lt;li&gt;Great operating cost (free tier lets you log &lt;em&gt;a lot&lt;/em&gt; of Pester results every month)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you don&#39;t already have a Log Analytics workspace I did a fairly detailed write-up last month: &lt;a href=&quot;https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/&quot;&gt;Getting started with Log Analytics and PowerShell logging&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;pester-result-schema&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/#pester-result-schema&quot;&gt;Pester Result Schema&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While we could log an absolutely minimal object to Log Analytics, I&#39;ve found that adding a little more structure is helpful for both debugging and analysing test results.&lt;/p&gt;
&lt;p&gt;We&#39;ll be building the result objects in PowerShell, and then sending the objects to be stored in a table in Log Analytics. The below table shows the property names, as well as their column name in the table in Log Analytics. Columns are suffixed with their data type when created in Log Analytics, shown below as their LA Name (Log Analytics Name).&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;LA Name&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Identifier&lt;/td&gt;
&lt;td&gt;Identifier_s&lt;/td&gt;
&lt;td&gt;Used to identify groups of Pester results, e.g. SQL Infra, AD Test, Fileshare Tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BatchId&lt;/td&gt;
&lt;td&gt;BatchId_g&lt;/td&gt;
&lt;td&gt;Unique per batch of tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;InvocationId&lt;/td&gt;
&lt;td&gt;InvocationId_g&lt;/td&gt;
&lt;td&gt;Unique per call to Invoke-Pester&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;InvocationStartTime&lt;/td&gt;
&lt;td&gt;TimeGenerated&lt;/td&gt;
&lt;td&gt;The time the invocation started&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;InvocationEndTime&lt;/td&gt;
&lt;td&gt;InvocationEndTime_t&lt;/td&gt;
&lt;td&gt;The time the invocation ended&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HostComputer&lt;/td&gt;
&lt;td&gt;HostComputer_s&lt;/td&gt;
&lt;td&gt;The computer running the tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target&lt;/td&gt;
&lt;td&gt;Target_s&lt;/td&gt;
&lt;td&gt;The target of the test (e.g. Host, SQL Instance)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TimeTaken&lt;/td&gt;
&lt;td&gt;TimeTaken_d&lt;/td&gt;
&lt;td&gt;The time taken for the test to execute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Passed&lt;/td&gt;
&lt;td&gt;Passed_b&lt;/td&gt;
&lt;td&gt;If the test passed or failed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Describe&lt;/td&gt;
&lt;td&gt;Describe_s&lt;/td&gt;
&lt;td&gt;The describe text for the test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context&lt;/td&gt;
&lt;td&gt;Context_s&lt;/td&gt;
&lt;td&gt;The context text for the test, if present&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Name&lt;/td&gt;
&lt;td&gt;Name_s&lt;/td&gt;
&lt;td&gt;The name text for the test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FailureMessage&lt;/td&gt;
&lt;td&gt;FailureMessage_s&lt;/td&gt;
&lt;td&gt;The error message for the test, if present&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Result&lt;/td&gt;
&lt;td&gt;Result_s&lt;/td&gt;
&lt;td&gt;The result of the test&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;We&#39;re going to map the value of &lt;code&gt;InvocationStartTime&lt;/code&gt; to the built-in field &lt;code&gt;TimeGenerated&lt;/code&gt;. If no field is supplied, &lt;code&gt;TimeGenerated&lt;/code&gt; defaults to ingestion time.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The great thing about the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/log-analytics/log-analytics-data-collector-api&quot;&gt;Data Collector API&lt;/a&gt; is that these fields are all optional, and so if you don&#39;t want to use the full schema you don&#39;t have to (perhaps your tests won&#39;t use context, or you won&#39;t care about host/target).&lt;/p&gt;
&lt;p&gt;Some columns that deserve a little more explanation are &lt;code&gt;BatchId&lt;/code&gt; and &lt;code&gt;InvocationId&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;batches-and-invocations&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/#batches-and-invocations&quot;&gt;Batches and Invocations&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Most infrastructure tests I run tend to come in a format that looks something like the following (pseudocode):&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$thingToTest&lt;/span&gt; in &lt;span class=&quot;token variable&quot;&gt;$listOfThingsToTest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;Invoke-Pester&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Script @&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; TestParameters = &lt;span class=&quot;token variable&quot;&gt;$thingToTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The execution of the whole script would be a &lt;strong&gt;Batch&lt;/strong&gt;. Every call to &lt;code&gt;Invoke-Pester&lt;/code&gt; is a separate &lt;strong&gt;Invocation&lt;/strong&gt;, which can have zero or more test results (technically an invocation has 0..N Describes, each of which has 0..N Contexts, each of which has 0..N Tests).&lt;/p&gt;
&lt;p&gt;Being able to look at batches &amp;amp; invocations will let you detect issues like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Incomplete batches (a hard-error knocked it out half-way)&lt;/li&gt;
&lt;li&gt;Overall runtime vs. Invocation runtime (vs. test runtime)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Rather than collecting your results and posting them in one go, I would encourage you to post them after every Invoke-Pester call. There are times when your automation will fail, and having incomplete results will assist in telling you how far your batch got before failing (vs. having no results if you wait until the end to try and post them).&lt;/p&gt;
&lt;p&gt;With batches and invocations our pseudocode now looks something like this (I&#39;ve also included an example call to post data to Log Analytics):&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$batchId&lt;/span&gt; = &lt;span class=&quot;token namespace&quot;&gt;[System.Guid]&lt;/span&gt;::NewGuid&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$thingToTest&lt;/span&gt; in &lt;span class=&quot;token variable&quot;&gt;$listOfThingsToTest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token variable&quot;&gt;$invocationId&lt;/span&gt; = &lt;span class=&quot;token namespace&quot;&gt;[System.Guid]&lt;/span&gt;::NewGuid&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token variable&quot;&gt;$results&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Invoke-Pester&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Script @&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; TestParameters = &lt;span class=&quot;token variable&quot;&gt;$thingToTest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;PassThru&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;Export-LogAnalytics&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Batch &lt;span class=&quot;token variable&quot;&gt;$batchId&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Invocation &lt;span class=&quot;token variable&quot;&gt;$invocationId&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Results &lt;span class=&quot;token variable&quot;&gt;$results&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note the usage of the &lt;code&gt;PassThru&lt;/code&gt; switch - this is required to capture the results of the Pester tests in the &lt;code&gt;$results&lt;/code&gt; variable.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;Export-LogAnalytics&lt;/code&gt; function is part of &lt;a href=&quot;https://github.com/taddison/SQLChecks&quot;&gt;SQLChecks&lt;/a&gt;, though you can build something very similar with the example PowerShell on the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/log-analytics/log-analytics-data-collector-api&quot;&gt;Data Collector API&lt;/a&gt; page. You can see the definition of the function on GitHub - &lt;a href=&quot;https://github.com/taddison/SQLChecks/blob/44e1b4461fa7be9279eaecc206cfe3dca25e2250/src/SQLChecks/Functions/Public/Export-LogAnalytics.ps1&quot;&gt;ExportLogAnalytics function&lt;/a&gt;. The source to the referenced function is also available on GitHub - &lt;a href=&quot;https://github.com/taddison/SQLChecks/blob/44e1b4461fa7be9279eaecc206cfe3dca25e2250/src/SQLChecks/Functions/Private/Get-LogAnalyticsSignature.ps1&quot;&gt;Get-LogAnalyticsSignature&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;working-with-the-pester-result-object&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/#working-with-the-pester-result-object&quot;&gt;Working with the Pester result object&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The object returned from &lt;code&gt;Invoke-Pester&lt;/code&gt; needs a bit of work to transform it into the schema we outlined above. The &lt;code&gt;$results&lt;/code&gt; object contains a property &lt;code&gt;TestResult&lt;/code&gt;, which is an array of result objects (one object for every test executed).&lt;/p&gt;
&lt;p&gt;Each result object contains information about the Describe, Context, and Test, as well as the result (pass/fail) and timing information. We use the &lt;code&gt;TestResult&lt;/code&gt; to build our array of PesterResult objects to sent to Log Analytics:&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$results&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Invoke-Pester&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;PassThru&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$pesterResults&lt;/span&gt; = @&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$testResult&lt;/span&gt; in &lt;span class=&quot;token variable&quot;&gt;$results&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TestResult&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token variable&quot;&gt;$pesterResults&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;[PSCustomObject]&lt;/span&gt;@&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  TimeTaken  = &lt;span class=&quot;token variable&quot;&gt;$testResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Time&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TotalMilliseconds&lt;br /&gt;  Passed = &lt;span class=&quot;token variable&quot;&gt;$testResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Passed&lt;br /&gt;  Describe = &lt;span class=&quot;token variable&quot;&gt;$testResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Describe&lt;br /&gt;  Context = &lt;span class=&quot;token variable&quot;&gt;$testResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Context&lt;br /&gt;  Name = &lt;span class=&quot;token variable&quot;&gt;$testResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;br /&gt;  FailureMessage = &lt;span class=&quot;token variable&quot;&gt;$testResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;FailureMessage&lt;br /&gt;  Result = &lt;span class=&quot;token variable&quot;&gt;$testResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Result&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# $exportLogAnalyticsArguments contains customerId, sharedKey, etc.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Export-LogAnalytics&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$pesterResults&lt;/span&gt; @exportLogAnalyticsArguments&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above example extracts the data from the Pester test results and no more (missing are things like &lt;code&gt;BatchId&lt;/code&gt;, &lt;code&gt;Target&lt;/code&gt;, etc.). Note that the above code is perfectly valid and can be used to quickly get started logging results.&lt;/p&gt;
&lt;h3 id=&quot;a-more-complex-example&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/#a-more-complex-example&quot;&gt;A more complex example&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A more complete example is shown below - this is taken from &lt;a href=&quot;https://github.com/taddison/SQLChecks&quot;&gt;SQLChecks&lt;/a&gt; which iterates over configuration files and performs one call to &lt;code&gt;Invoke-Pester&lt;/code&gt; (wrapped by &lt;code&gt;Invoke-SQLChecks&lt;/code&gt;) per file being tested - in this case each file represents an instance of SQL Server.&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$batchId&lt;/span&gt; = &lt;span class=&quot;token namespace&quot;&gt;[System.Guid]&lt;/span&gt;::NewGuid&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$logAnalyticsArguments&lt;/span&gt; = @&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    CustomerId = &lt;span class=&quot;token string&quot;&gt;&quot;xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&quot;&lt;/span&gt;&lt;br /&gt;    SharedKey = &lt;span class=&quot;token string&quot;&gt;&quot;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&quot;&lt;/span&gt;&lt;br /&gt;    LogType = &lt;span class=&quot;token string&quot;&gt;&quot;PesterResult&quot;&lt;/span&gt;&lt;br /&gt;    TimeStampField = &lt;span class=&quot;token string&quot;&gt;&quot;InvocationStartTime&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$configs&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-ChildItem&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Filter&lt;/span&gt; localhost&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;json &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Recurse&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$configFile&lt;/span&gt; in &lt;span class=&quot;token variable&quot;&gt;$configs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$config&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Read-SqlChecksConfig&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$configFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;FullName&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$targetComputer&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ServerInstance&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$invocationStartTime&lt;/span&gt; = &lt;span class=&quot;token namespace&quot;&gt;[DateTime]&lt;/span&gt;::UtcNow&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$results&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Invoke-SqlChecks&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Config &lt;span class=&quot;token variable&quot;&gt;$config&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;PassThru &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Show None&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$invocationEndTime&lt;/span&gt; = &lt;span class=&quot;token namespace&quot;&gt;[DateTime]&lt;/span&gt;::UtcNow&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;# If the config has no tests, nothing to post in this batch&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$results&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Count &lt;span class=&quot;token operator&quot;&gt;-gt&lt;/span&gt; 0&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$pesterResults&lt;/span&gt; = @&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$testResult&lt;/span&gt; in &lt;span class=&quot;token variable&quot;&gt;$results&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TestResult&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token variable&quot;&gt;$pesterResults&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;[PSCustomObject]&lt;/span&gt;@&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                BatchId = &lt;span class=&quot;token variable&quot;&gt;$BatchId&lt;/span&gt;&lt;br /&gt;                InvocationId = &lt;span class=&quot;token namespace&quot;&gt;[System.Guid]&lt;/span&gt;::NewGuid&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                InvocationStartTime = &lt;span class=&quot;token variable&quot;&gt;$InvocationStartTime&lt;/span&gt;&lt;br /&gt;                InvocationEndTime = &lt;span class=&quot;token variable&quot;&gt;$InvocationEndTime&lt;/span&gt;&lt;br /&gt;                HostComputer = &lt;span class=&quot;token variable&quot;&gt;$env&lt;/span&gt;:computername&lt;br /&gt;                Target = &lt;span class=&quot;token variable&quot;&gt;$TargetComputer&lt;/span&gt;&lt;br /&gt;                TimeTaken = &lt;span class=&quot;token variable&quot;&gt;$testResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Time&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TotalMilliseconds&lt;br /&gt;                Passed = &lt;span class=&quot;token variable&quot;&gt;$testResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Passed&lt;br /&gt;                Describe = &lt;span class=&quot;token variable&quot;&gt;$testResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Describe&lt;br /&gt;                Context = &lt;span class=&quot;token variable&quot;&gt;$testResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Context&lt;br /&gt;                Name = &lt;span class=&quot;token variable&quot;&gt;$testResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;br /&gt;                FailureMessage = &lt;span class=&quot;token variable&quot;&gt;$testResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;FailureMessage&lt;br /&gt;                Result = &lt;span class=&quot;token variable&quot;&gt;$testResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Result&lt;br /&gt;                Identifier = &lt;span class=&quot;token string&quot;&gt;&quot;SQLChecks&quot;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token function&quot;&gt;Export-LogAnalytics&lt;/span&gt; @logAnalyticsArguments &lt;span class=&quot;token variable&quot;&gt;$pesterResults&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;a-more-complex-example-simplified&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/#a-more-complex-example-simplified&quot;&gt;A more complex example, simplified&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Because this is a lot of code to write everywhere you deploy &lt;a href=&quot;https://github.com/taddison/SQLChecks&quot;&gt;SQLChecks&lt;/a&gt;, it has been wrapped into a function - &lt;code&gt;Invoke-SqlChecksToLogAnalytics&lt;/code&gt;, which means you can reduce the above example to the following:&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$batchId&lt;/span&gt; = &lt;span class=&quot;token namespace&quot;&gt;[System.Guid]&lt;/span&gt;::NewGuid&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$logAnalyticsArguments&lt;/span&gt; = @&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    CustomerId = &lt;span class=&quot;token string&quot;&gt;&quot;xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&quot;&lt;/span&gt;&lt;br /&gt;    SharedKey = &lt;span class=&quot;token string&quot;&gt;&quot;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$configs&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-ChildItem&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Filter&lt;/span&gt; localhost&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;json &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Recurse&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$configFile&lt;/span&gt; in &lt;span class=&quot;token variable&quot;&gt;$configs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$config&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Read-SqlChecksConfig&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$configFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;FullName&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;Invoke-SqlChecksToLogAnalytics&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Config &lt;span class=&quot;token variable&quot;&gt;$config&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;BatchId &lt;span class=&quot;token variable&quot;&gt;$batchId&lt;/span&gt; @logAnalyticsArguments&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The definition of the &lt;a href=&quot;https://github.com/taddison/SQLChecks/blob/44e1b4461fa7be9279eaecc206cfe3dca25e2250/src/SQLChecks/Functions/Public/Invoke-SqlChecksToLogAnalytics.ps1&quot;&gt;Invoke-SqlChecksToLogAnalytics function&lt;/a&gt; shows how you might want to wrap your own infrastructure test library.&lt;/p&gt;
&lt;h2 id=&quot;querying-results&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/#querying-results&quot;&gt;Querying results&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Once you have some results in Log Analytics you can start to query them. You can get to the query interface via the Azure Portal, and once there to look at our query results we&#39;d write:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PesterResult_CL
| order by TimeGenerated desc
| take 100
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;showing-recent-batches&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/#showing-recent-batches&quot;&gt;Showing recent batches&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As there might be many kinds of Pester tests being shipped, we&#39;ll typically want to focus on a specific set - we&#39;ll use &lt;a href=&quot;https://github.com/taddison/SQLChecks&quot;&gt;SQLChecks&lt;/a&gt; as an example again. The following code will find the most recent batch from the last 7 days, and show all results:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PesterResult_CL
| where TimeGenerated &amp;gt; ago(7d)
| where Identifier_s == &amp;quot;SQLChecks&amp;quot;
| top-nested 1 of BatchId_g by max(TimeGenerated)
| join kind= inner (
   PesterResult_CL
) on BatchId_g
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While the most recent batch is a pretty common requirement, you may have different batch sizes (people running ad-hoc tests in the day are one example of smaller batches). One method I&#39;ve used to find the most recent &lt;em&gt;complete&lt;/em&gt; batch -is to look for queries that contain more than &lt;code&gt;N&lt;/code&gt; results - I know my typical SQL checks have 900 tests, so the below query lets me filter out any small ad-hoc or incomplete batches:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PesterResult_CL
| where TimeGenerated &amp;gt; ago(7d)
| where Identifier_s == &amp;quot;SQLChecks&amp;quot;
| summarize testCount = count(), maxTime = max(TimeGenerated) by BatchId_g
| where testCount &amp;gt; 900
| top-nested 1 of BatchId_g by max(maxTime)
| join kind= inner (
   PesterResult_CL
) on BatchId_g
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;showing-failures-only&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/#showing-failures-only&quot;&gt;Showing failures only&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When there are failures, you can quickly view details by filtering on the boolean column &lt;code&gt;Passed_b&lt;/code&gt;. The below projects only the essential columns:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PesterResult_CL
| where TimeGenerated &amp;gt; ago(7d)
| where Identifier_s == &amp;quot;SQLChecks&amp;quot;
| summarize testCount = count(), maxTime = max(TimeGenerated) by BatchId_g
| where testCount &amp;gt; 900
| top-nested 1 of BatchId_g by max(maxTime)
| join kind= inner (
   PesterResult_CL
) on BatchId_g
| where not(Passed_b)
| project TimeGenerated, Describe_s, Name_s, FailureMessage_s
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;In this specific example the result means that the trace flags configured on the server differ from the expected trace flags by a count of one.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/U7S9lOnbOi-295.avif 295w, https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/U7S9lOnbOi-590.avif 590w, https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/U7S9lOnbOi-885.avif 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/U7S9lOnbOi-295.webp 295w, https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/U7S9lOnbOi-590.webp 590w, https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/U7S9lOnbOi-885.webp 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/U7S9lOnbOi-295.jpeg 295w, https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/U7S9lOnbOi-590.jpeg 590w, https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/U7S9lOnbOi-885.jpeg 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;One test failure&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/U7S9lOnbOi-295.jpeg&quot; width=&quot;885&quot; height=&quot;142&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h3 id=&quot;showing-batch-aggregates&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/#showing-batch-aggregates&quot;&gt;Showing batch aggregates&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To look at the overall stats (tests, passed, failed) we can group by any set of columns - in the below example we&#39;re grouping by &lt;code&gt;Target&lt;/code&gt; and &lt;code&gt;Describe&lt;/code&gt;, and then ordering by the number of failed tests. This lets us quickly see which tests have failed and against what target.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PesterResult_CL
| where TimeGenerated &amp;gt; ago(7d)
| where Identifier_s == &amp;quot;SQLChecks&amp;quot;
| summarize testCount = count(), maxTime = max(TimeGenerated) by BatchId_g
| where testCount &amp;gt; 900
| top-nested 1 of BatchId_g by max(maxTime)
| join kind= inner (
   PesterResult_CL
) on BatchId_g
| summarize TestCount = count(), Passed = sumif(1,Passed_b), Failed = sumif(1,not(Passed_b))  by Target_s, Describe_s
| order by Failed desc
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;In this case it looks like we have some data file space issues in addition to the trace flag problem.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/ixogbuDU4E-295.avif 295w, https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/ixogbuDU4E-590.avif 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/ixogbuDU4E-295.webp 295w, https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/ixogbuDU4E-590.webp 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/ixogbuDU4E-295.jpeg 295w, https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/ixogbuDU4E-590.jpeg 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Test Summary&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/ixogbuDU4E-295.jpeg&quot; width=&quot;590&quot; height=&quot;321&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h3 id=&quot;showing-test-or-machine-history&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/#showing-test-or-machine-history&quot;&gt;Showing test or machine history&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We might want to look at how a single test is performing over the estate. The below query shows the status of the &lt;code&gt;Data file space used&lt;/code&gt; Describe by percent success (0% = all tests failed, 100% = all tests passed), split by target. Note we multiply the count by 1.0 to turn it into a float, rather than an integer (which would floor our result to always 0 or 1).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PesterResult_CL
| where TimeGenerated &amp;gt; ago(30d)
| where Identifier_s == &amp;quot;SQLChecks&amp;quot;
| where Describe_s == &amp;quot;Data file space used&amp;quot;
| summarize Passed = sumif(1,Passed_b), Total = count() * 1.0 by Target_s, bin(TimeGenerated, 1d)
| extend PercentSuccess = Passed / Total
| project TimeGenerated, Target_s, PercentSuccess
| render timechart
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;The below graph shows an example of a few targets which have never had a failure (yay!), one target which was partially failing (note the Y axis starts at 0.5) for a long time and recently was fixed, and another which partially failed and then recovered.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/NkFki9ZR4y-295.avif 295w, https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/NkFki9ZR4y-590.avif 590w, https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/NkFki9ZR4y-885.avif 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/NkFki9ZR4y-295.webp 295w, https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/NkFki9ZR4y-590.webp 590w, https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/NkFki9ZR4y-885.webp 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/NkFki9ZR4y-295.jpeg 295w, https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/NkFki9ZR4y-590.jpeg 590w, https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/NkFki9ZR4y-885.jpeg 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Test pass rate&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/NkFki9ZR4y-295.jpeg&quot; width=&quot;885&quot; height=&quot;310&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h3 id=&quot;finding-the-longest-running-describe-block&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/#finding-the-longest-running-describe-block&quot;&gt;Finding the longest-running Describe block&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you&#39;re looking to performance tune your infrastructure tests, you&#39;ll want to know where the time is being spent. This final example shows how you can find which one of the describe blocks is taking the longest time to run. The example uses the most recent batch and plots the time taken in milliseconds for each describe block.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PesterResult_CL
| where TimeGenerated &amp;gt; ago(7d)
| where Identifier_s == &amp;quot;SQLChecks&amp;quot;
| top-nested 1 of BatchId_g by max(TimeGenerated)
| join kind= inner (
   PesterResult_CL
) on BatchId_g
| top-nested 5 of Describe_s by sum(TimeTaken_d)
| order by aggregated_Describe_s desc
| render barchart
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;In this example checking for &lt;code&gt;Duplicate Indexes&lt;/code&gt; dominates at almost 140 seconds.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/YTZ9B-V9At-295.avif 295w, https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/YTZ9B-V9At-590.avif 590w, https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/YTZ9B-V9At-885.avif 885w, https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/YTZ9B-V9At-1180.avif 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/YTZ9B-V9At-295.webp 295w, https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/YTZ9B-V9At-590.webp 590w, https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/YTZ9B-V9At-885.webp 885w, https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/YTZ9B-V9At-1180.webp 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/YTZ9B-V9At-295.jpeg 295w, https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/YTZ9B-V9At-590.jpeg 590w, https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/YTZ9B-V9At-885.jpeg 885w, https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/YTZ9B-V9At-1180.jpeg 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Test duration&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/YTZ9B-V9At-295.jpeg&quot; width=&quot;1180&quot; height=&quot;395&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h2 id=&quot;summary&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/#summary&quot;&gt;Summary&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Well done if you made it this far - if you&#39;re starting from scratch with your infrastructure testing there are a lot of steps needed to get here. The good news is that once you&#39;ve gone through all this setup for your first set of tests, onboarding and analysing the results from subsequent tests is very easy.&lt;/p&gt;
&lt;p&gt;By having all your Pester results stored in Log Analytics you&#39;re able to inspect the health of your estate now and historically very quickly, and can additionally share access to those test results directly (giving people the ability to write their own queries over your results), or create and share dashboards (perhaps leveraging the ability of Power BI to query Log Analytics). Some other options you have to leverage your results in Log Analytics include creating alerts with Azure Monitor (alert on Pester failures), or scheduling periodic reports with Flow (a daily summary of Pester results).&lt;/p&gt;
&lt;p&gt;In the near future I&#39;ll be showing how you can use Pester to perform data validation checks too - the results will, of course, be shipped to Log Analytics for easy querying/monitoring/alerting.&lt;/p&gt;
</description>
      <pubDate>Sun, 23 Sep 2018 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2018/09/saving-and-reporting-on-pester-infrastructure-test-results-in-log-analytics/</guid>
    </item>
    <item>
      <title>Getting started with Log Analytics and PowerShell logging</title>
      <link>https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/</link>
      <description>&lt;p&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/log-analytics/log-analytics-overview&quot;&gt;Log Analytics&lt;/a&gt; is a fantastic place to ship, store, and analyse your logs. Whether they&#39;re coming from a linked Azure resource, machine agents, or you&#39;re posting them from your own applications and services, Log Analytics is a key part of &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/monitoring/&quot;&gt;Azure Management &amp;amp; Monitoring&lt;/a&gt;. Whether you&#39;re an IT Pro, working in devops, or an application developer - this platform and its capabilities are worth exploring and understanding.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Log Analytics was previously offered as part of the Operations Management Suite (OMS) bundling, though that labelling is in the process of being retired.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To get started you can create a free workspace which lets you ingest up to 5GB of data per month. Once the data is loaded there is no cost to query it, and it&#39;ll be retained for 31 days (you can up retention and ingestion limits as part of paid plan later).&lt;/p&gt;
&lt;p&gt;This post will walk through creating a Log Analytics workspace, uploading some logs with PowerShell, and then querying them via the portal. To follow along you&#39;ll need an Azure subscription and the AzureRM PowerShell module - for installation instructions see the &lt;a href=&quot;https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/#prerequisites&quot;&gt;prerequisites&lt;/a&gt; section at the end of this post.&lt;/p&gt;
&lt;h2 id=&quot;creating-the-workspace&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/#creating-the-workspace&quot;&gt;Creating the workspace&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We&#39;ll create the workspace using PowerShell. If you prefer you can &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/log-analytics/log-analytics-quick-create-workspace&quot;&gt;create the workspace via the Azure Portal&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To start you&#39;ll need to import the AzureRM PowerShell module and connect to your Azure account. If you have more than one subscription you&#39;ll need to select the one you want to work with.&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;Import-Module&lt;/span&gt; AzureRm&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Connect-AzureRmAccount&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# If you have more than one subscription select the one you want to create the workspace in&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Get-AzureRmSubscription&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;SubscriptionName &lt;span class=&quot;token string&quot;&gt;&quot;Sub Name&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Select-AzureRmSubscription&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once connected to a subscription you can create a workspace. The below code creates it in the &lt;code&gt;East US&lt;/code&gt; location, though there are a few &lt;a href=&quot;https://azure.microsoft.com/en-us/global-infrastructure/services/&quot;&gt;other regions available&lt;/a&gt;. The script below also creates a resource group to store the workspace (though you could also specify an existing resource group).&lt;/p&gt;
&lt;p&gt;The resource group name must be unique within your subscription, and the workspace name must be globally (not just your subscription/organisation) unique.&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$resourceGroupName&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;RG-LogAnalytics-Test&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$location&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;eastus&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$workspaceName&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;LogAnalyticsTestWorkspace&quot;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;New-AzureRmResourceGroup&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Name &lt;span class=&quot;token variable&quot;&gt;$resourceGroupName&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Location &lt;span class=&quot;token variable&quot;&gt;$location&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$newWorkspaceParams&lt;/span&gt; = @&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    ResourceGroupName = &lt;span class=&quot;token variable&quot;&gt;$resourceGroupName&lt;/span&gt;&lt;br /&gt;    Location = &lt;span class=&quot;token variable&quot;&gt;$location&lt;/span&gt;&lt;br /&gt;    Sku = &lt;span class=&quot;token string&quot;&gt;&quot;standalone&quot;&lt;/span&gt;&lt;br /&gt;    Name = &lt;span class=&quot;token variable&quot;&gt;$workspaceName&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;New-AzureRmOperationalInsightsWorkspace&lt;/span&gt; @newWorkspaceParams&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Note the SKU is actually &lt;code&gt;PerGB2018&lt;/code&gt; - the older SKUs (free, standard, premium, standalone, pernode) aren&#39;t supported in subscriptions created after April 2018. You can read more details in the &lt;a href=&quot;https://azure.microsoft.com/en-gb/blog/introducing-a-new-way-to-purchase-azure-monitoring-services/&quot;&gt;announcement blog post&lt;/a&gt;. We specify the &lt;code&gt;standalone&lt;/code&gt; SKU though it&#39;ll actually create with &lt;code&gt;PerGB2018&lt;/code&gt; (the AzureRM cmdlets don&#39;t yet support the &lt;code&gt;PerGB2018&lt;/code&gt; SKU).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The last thing we&#39;ll do is apply a data volume cap to ensure our workspace doesn&#39;t exceed the free tier&#39;s limit of 5GB/month. Currently this can&#39;t be done with a PowerShell cmdlet, so you&#39;ll need to use the portal.&lt;/p&gt;
&lt;p&gt;The screenshot below shows what this looks like, the steps you need to take from the Log Analytics page are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Select the &lt;strong&gt;Usage &amp;amp; Billing&lt;/strong&gt; blade&lt;/li&gt;
&lt;li&gt;Select the &lt;strong&gt;Data volume management&lt;/strong&gt; tab&lt;/li&gt;
&lt;li&gt;Turn the &lt;strong&gt;Daily Volume Cap&lt;/strong&gt; ON&lt;/li&gt;
&lt;li&gt;Set a volume which keeps you within your 5GB/month limit (e.g. 0.15GB/day)&lt;/li&gt;
&lt;li&gt;Press OK to apply the settings&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/hcSViGyp-9-295.avif 295w, https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/hcSViGyp-9-590.avif 590w, https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/hcSViGyp-9-885.avif 885w, https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/hcSViGyp-9-1180.avif 1180w, https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/hcSViGyp-9-1475.avif 1475w, https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/hcSViGyp-9-1770.avif 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/hcSViGyp-9-295.webp 295w, https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/hcSViGyp-9-590.webp 590w, https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/hcSViGyp-9-885.webp 885w, https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/hcSViGyp-9-1180.webp 1180w, https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/hcSViGyp-9-1475.webp 1475w, https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/hcSViGyp-9-1770.webp 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/hcSViGyp-9-295.jpeg 295w, https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/hcSViGyp-9-590.jpeg 590w, https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/hcSViGyp-9-885.jpeg 885w, https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/hcSViGyp-9-1180.jpeg 1180w, https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/hcSViGyp-9-1475.jpeg 1475w, https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/hcSViGyp-9-1770.jpeg 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Set daily volume cap&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/hcSViGyp-9-295.jpeg&quot; width=&quot;1770&quot; height=&quot;1115&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I&#39;ve &lt;a href=&quot;https://github.com/Azure/azure-powershell/issues/7053&quot;&gt;opened an issue&lt;/a&gt; to request adding support to set daily volume caps in the AzureRM PowerShell module.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;posting-data-to-the-workspace&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/#posting-data-to-the-workspace&quot;&gt;Posting data to the workspace&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To post data to the workspace you&#39;ll need one of the two &lt;em&gt;workspace keys&lt;/em&gt; as well as the CustomerId (sometimes called the Workspace Id). You can get this information from the portal, or via PowerShell:&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;Get-AzureRmOperationalInsightsWorkspaceSharedKeys&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ResourceGroupName &lt;span class=&quot;token variable&quot;&gt;$resourceGroupName&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Name &lt;span class=&quot;token variable&quot;&gt;$workspaceName&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Get-AzureRmOperationalInsightsWorkspace&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ResourceGroupName &lt;span class=&quot;token variable&quot;&gt;$resourceGroupName&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Name &lt;span class=&quot;token variable&quot;&gt;$workspaceName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CustomerId&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To send data to our workspace we&#39;re going to use the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/log-analytics/log-analytics-data-collector-api&quot;&gt;Log Analytics Data Collector API&lt;/a&gt;. This API lets us send a JSON payload which gets logged to a table in of our workspace. We&#39;re going to use the &lt;code&gt;Export-LogAnalytics&lt;/code&gt; function (available in &lt;a href=&quot;https://gist.github.com/taddison/d49bd8c6f7fc1d45aa8e7b0906c180ae&quot;&gt;this GitHub Gist&lt;/a&gt;) to send the data.&lt;/p&gt;
&lt;p&gt;Our test dataset will be a snapshot of currently running processes, including their name and handle count.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Export-LogAnalytics&lt;/code&gt; function uses &lt;code&gt;ConvertTo-Json&lt;/code&gt; to build the JSON payload, which by default attempts to serialize the object with depth 2. By building our own &lt;code&gt;pscustomobject&lt;/code&gt; we have fine-grained control over exactly what schema our final table will have (and in most cases you won&#39;t want to log every property on the typically data-rich PowerShell objects you are working with).&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$processes&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-Process&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ForEach-Object&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token namespace&quot;&gt;[pscustomobject]&lt;/span&gt;@&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        ProcessId = &lt;span class=&quot;token variable&quot;&gt;$_&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Id&lt;br /&gt;        ProcessName = &lt;span class=&quot;token variable&quot;&gt;$_&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ProcessName&lt;br /&gt;        Handles = &lt;span class=&quot;token variable&quot;&gt;$_&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;HandleCount&lt;br /&gt;        CollectionTime = &lt;span class=&quot;token namespace&quot;&gt;[System.DateTime]&lt;/span&gt;::UtcNow&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$logAnalyticsParams&lt;/span&gt; = @&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    CustomerId = &lt;span class=&quot;token string&quot;&gt;&quot;From-Earlier-Step...&quot;&lt;/span&gt;&lt;br /&gt;    SharedKey = &lt;span class=&quot;token string&quot;&gt;&quot;From-Earlier-Step...&quot;&lt;/span&gt;&lt;br /&gt;    TimeStampField = &lt;span class=&quot;token string&quot;&gt;&quot;CollectionTime&quot;&lt;/span&gt;&lt;br /&gt;    LogType = &lt;span class=&quot;token string&quot;&gt;&quot;Process&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Export-LogAnalytics&lt;/span&gt; @logAnalyticsParams &lt;span class=&quot;token variable&quot;&gt;$processes&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A few things to note:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The table name is based on the &lt;code&gt;LogType&lt;/code&gt; argument, and always has _CL appended (for Custom Log)&lt;/li&gt;
&lt;li&gt;Every property will get its own column in the final table, typed as either a string, boolean, datetime, guid, or double&lt;/li&gt;
&lt;li&gt;The column name is suffixed based on the data type - &lt;code&gt;_d&lt;/code&gt; for double, &lt;code&gt;_t&lt;/code&gt; for datetime, etc.&lt;/li&gt;
&lt;li&gt;If TimeStampField isn&#39;t specified the time of every record is based on ingestion time, otherwise the TimeGenerated property is set based on the field specified
&lt;ul&gt;
&lt;li&gt;In our example TimeGenerated field will contain the value from the CollectionTime property, and CollectionTime will &lt;strong&gt;not&lt;/strong&gt; appear as a field in the table&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;If an array of objects are passed to the function, each object is ingested as a separate row&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;For more information about the Data Collector API check the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/log-analytics/log-analytics-data-collector-api&quot;&gt;documentation&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;viewing-the-data&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/#viewing-the-data&quot;&gt;Viewing the data&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Important note:&lt;/strong&gt; There can be significant delays between you posting the data and it being available to query. This is known as &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/log-analytics/log-analytics-data-ingestion-time&quot;&gt;ingestion delay&lt;/a&gt;, and the current SLA is 6 hours. Data typically appears within 10 minutes, and although your custom table (e.g. &lt;code&gt;Process_CL&lt;/code&gt;) will appear immediately once you post data, the fields won&#39;t appear until the data is ingested. If no rows appear, wait a little while!&lt;/p&gt;
&lt;p&gt;Once the data has been ingested you can navigate to the Log Analytics query interface via the Azure Portal (the &lt;strong&gt;Logs&lt;/strong&gt; blade), or you can run the below PowerShell to get a direct link to query your workspace:&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$subscriptionId&lt;/span&gt; = &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Get-AzureRmContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Subscription&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Id&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$uri&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;https://portal.loganalytics.io/subscriptions/{0}/resourcegroups/{1}/workspaces/{2}&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;f &lt;span class=&quot;token variable&quot;&gt;$subscriptionId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$resourceGroupName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$workspaceName&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$uri&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once in the query portal you can write a query (if you used the link from PowerShell you&#39;ll need to open a new tab). The below example lists processes ordered by the number of handles:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ProcessLog_CL
| order by Handles_d desc
| project TimeGenerated, Handles_d, ProcessName_s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/F_5aYc_DQS-295.avif 295w, https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/F_5aYc_DQS-590.avif 590w, https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/F_5aYc_DQS-885.avif 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/F_5aYc_DQS-295.webp 295w, https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/F_5aYc_DQS-590.webp 590w, https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/F_5aYc_DQS-885.webp 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/F_5aYc_DQS-295.jpeg 295w, https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/F_5aYc_DQS-590.jpeg 590w, https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/F_5aYc_DQS-885.jpeg 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Handling it&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/F_5aYc_DQS-295.jpeg&quot; width=&quot;885&quot; height=&quot;612&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;You can also deep-link queries using the URI we built earlier, appending the query you want to run (you&#39;ll need to URL encode the query if it contains spaces/line breaks/etc.):&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;{0}?query=Process_CL&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;f &lt;span class=&quot;token variable&quot;&gt;$uri&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;clean-up&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/#clean-up&quot;&gt;Clean-up&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you don&#39;t want to keep your workspace around you can tidy up by either removing the whole resource group (which deletes all resources in it), or just the workspace:&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Remove the whole resource group&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Remove-AzureRmResourceGroup&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ResourceGroupName &lt;span class=&quot;token variable&quot;&gt;$resourceGroupName&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# Just remove the log analytics workspace&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Remove-AzureRmOperationalInsightsWorkspace&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ResourceGroupName &lt;span class=&quot;token variable&quot;&gt;$resourceGroupName&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Name &lt;span class=&quot;token variable&quot;&gt;$workspaceName&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also use the Azure portal to delete either of these.&lt;/p&gt;
&lt;h2 id=&quot;next-steps&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/#next-steps&quot;&gt;Next steps&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The world is your data-driven oyster now you&#39;ve got a workspace configured! All you need now is some data - a great way to get started is by &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/log-analytics/log-analytics-agent-windows&quot;&gt;connecting your machine to log analytics&lt;/a&gt; and &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/log-analytics/log-analytics-data-sources-performance-counters&quot;&gt;shipping some performance counters&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once you&#39;ve got some data you&#39;ll want to get to grips with the query language of Log Analytics - KQL (Kusto Query Language). This language is making an appearance in more and more products (&lt;a href=&quot;https://azure.microsoft.com/en-us/services/application-insights/&quot;&gt;Application Insights&lt;/a&gt; and &lt;a href=&quot;https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-defender-atp/windows-defender-advanced-threat-protection&quot;&gt;Windows Defender Advanced Threat Protection&lt;/a&gt; are two particularly prominent ones), and is &lt;em&gt;extremely&lt;/em&gt; powerful. The &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-monitor/logs/log-analytics-overview&quot;&gt;documentation home page for Log Analytics&lt;/a&gt; is a good place to start, or you can dive straight into the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/data-explorer/kusto/query/&quot;&gt;query language reference&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This really is only a small taste of what you can do when you start leveraging Log Analytics as the glue for your monitoring and alerting (which is whole other facet of the product to explore). The below image (taken from Microsoft&#39;s &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/log-analytics/log-analytics-overview&quot;&gt;Log Analytics&lt;/a&gt; overview page) should give you plenty of ideas for how it can be used.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/XP8UmO_Aop-295.avif 295w, https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/XP8UmO_Aop-590.avif 590w, https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/XP8UmO_Aop-885.avif 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/XP8UmO_Aop-295.webp 295w, https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/XP8UmO_Aop-590.webp 590w, https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/XP8UmO_Aop-885.webp 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/XP8UmO_Aop-295.jpeg 295w, https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/XP8UmO_Aop-590.jpeg 590w, https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/XP8UmO_Aop-885.jpeg 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Log Analytics as glue&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/XP8UmO_Aop-295.jpeg&quot; width=&quot;885&quot; height=&quot;419&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;I hope you have as much fun querying your logs as I do!&lt;/p&gt;
&lt;hr /&gt;
&lt;h3 id=&quot;prerequisites&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/#prerequisites&quot;&gt;Prerequisites&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You can &lt;a href=&quot;https://azure.microsoft.com/en-us/free/&quot;&gt;create a free Azure Subscription&lt;/a&gt; if you don&#39;t already have one. You&#39;ll need a Microsoft Account to do that. The subscription comes with a bunch of services that are free for 12 months, along with some that are free forever (including 5GB per month of Log Analytics).&lt;/p&gt;
&lt;p&gt;If you want to use PowerShell to create your workspace and follow along with most of the examples in this post you&#39;ll need to &lt;a href=&quot;https://docs.microsoft.com/en-us/powershell/azure/azurerm/install-azurerm-ps&quot;&gt;install AzureRM PowerShell&lt;/a&gt; - the easiest way to do that is to execute the following from an elevated PowerShell prompt:&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;Install-Module&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Name AzureRM&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;full-workspace-creation-script&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/#full-workspace-creation-script&quot;&gt;Full workspace creation script&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The below script creates the resource group, workspace, and then prints the primary &amp;amp; secondary keys, as well as the URI for querying logs. You still need to visit the portal to set a daily cap if you want to avoid any charges.&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;Import-Module&lt;/span&gt; AzureRm&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Connect-AzureRmAccount&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$subscriptionName&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;Tim Addison - MSDN&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Get-AzureRmSubscription&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;SubscriptionName &lt;span class=&quot;token variable&quot;&gt;$subscriptionName&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Select-AzureRmSubscription&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$resourceGroupName&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;RG-LogAnalytics-Test&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# subscription unique&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$location&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;eastus&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$workspaceName&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;LogAnalyticsTestWorkspace&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# globally unique&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;New-AzureRmResourceGroup&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Name &lt;span class=&quot;token variable&quot;&gt;$resourceGroupName&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Location &lt;span class=&quot;token variable&quot;&gt;$location&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$newWorkspaceParams&lt;/span&gt; = @&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    ResourceGroupName = &lt;span class=&quot;token variable&quot;&gt;$resourceGroupName&lt;/span&gt;&lt;br /&gt;    Location = &lt;span class=&quot;token variable&quot;&gt;$location&lt;/span&gt;&lt;br /&gt;    Sku = &lt;span class=&quot;token string&quot;&gt;&quot;standard&quot;&lt;/span&gt;&lt;br /&gt;    Name = &lt;span class=&quot;token variable&quot;&gt;$workspaceName&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;New-AzureRmOperationalInsightsWorkspace&lt;/span&gt; @newWorkspaceParams&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Get-AzureRmOperationalInsightsWorkspaceSharedKeys&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ResourceGroupName &lt;span class=&quot;token variable&quot;&gt;$resourceGroupName&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Name &lt;span class=&quot;token variable&quot;&gt;$workspaceName&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Get-AzureRmOperationalInsightsWorkspace&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ResourceGroupName &lt;span class=&quot;token variable&quot;&gt;$resourceGroupName&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Name &lt;span class=&quot;token variable&quot;&gt;$workspaceName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CustomerId&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$subscriptionId&lt;/span&gt; = &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Get-AzureRmContext&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Subscription&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Id&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$uri&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;https://portal.loganalytics.io/subscriptions/{0}/resourcegroups/{1}/workspaces/{2}&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;f &lt;span class=&quot;token variable&quot;&gt;$subscriptionId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$resourceGroupName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$workspaceName&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$uri&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;export-loganalytics-function&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/#export-loganalytics-function&quot;&gt;Export-LogAnalytics function&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The below script contains the &lt;code&gt;Export-LogAnalytics&lt;/code&gt; function and supporting &lt;code&gt;Get-LogAnalyticsSignature&lt;/code&gt; function. They are both derived from the examples on the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/log-analytics/log-analytics-data-collector-api&quot;&gt;Log Analytics Data Collector API&lt;/a&gt; page.&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Adapted from https://docs.microsoft.com/en-us/azure/log-analytics/log-analytics-data-collector-api&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;Function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Get-LogAnalyticsSignature&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token namespace&quot;&gt;[cmdletbinding()]&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;Param&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$customerId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$sharedKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$contentLength&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$method&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$contentType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$resource&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$xHeaders&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;x-ms-date:&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$date&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$stringToHash&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$method&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;`n&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$contentLength&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;`n&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$contentType&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;`n&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$xHeaders&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;`n&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$resource&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$bytesToHash&lt;/span&gt; = &lt;span class=&quot;token namespace&quot;&gt;[Text.Encoding]&lt;/span&gt;::UTF8&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;GetBytes&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$stringToHash&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$keyBytes&lt;/span&gt; = &lt;span class=&quot;token namespace&quot;&gt;[Convert]&lt;/span&gt;::FromBase64String&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$sharedKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$sha256&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;New-Object&lt;/span&gt; System&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Security&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Cryptography&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;HMACSHA256&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$sha256&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Key = &lt;span class=&quot;token variable&quot;&gt;$keyBytes&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$calculatedHash&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$sha256&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ComputeHash&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$bytesToHash&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$encodedHash&lt;/span&gt; = &lt;span class=&quot;token namespace&quot;&gt;[Convert]&lt;/span&gt;::ToBase64String&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$calculatedHash&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$authorization&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&#39;SharedKey {0}:{1}&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;f &lt;span class=&quot;token variable&quot;&gt;$customerId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$encodedHash&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$authorization&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;Function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Export-LogAnalytics&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token namespace&quot;&gt;[cmdletbinding()]&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;Param&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$customerId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$sharedKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$logType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$TimeStampField&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$bodyAsJson&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;ConvertTo-Json&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$object&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$body&lt;/span&gt; = &lt;span class=&quot;token namespace&quot;&gt;[System.Text.Encoding]&lt;/span&gt;::UTF8&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;GetBytes&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$bodyAsJson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$method&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;POST&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$contentType&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$resource&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;/api/logs&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$rfc1123date&lt;/span&gt; = &lt;span class=&quot;token namespace&quot;&gt;[DateTime]&lt;/span&gt;::UtcNow&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ToString&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;r&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$contentLength&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Length&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$signatureArguments&lt;/span&gt; = @&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        CustomerId = &lt;span class=&quot;token variable&quot;&gt;$customerId&lt;/span&gt;&lt;br /&gt;        SharedKey = &lt;span class=&quot;token variable&quot;&gt;$sharedKey&lt;/span&gt;&lt;br /&gt;        Date = &lt;span class=&quot;token variable&quot;&gt;$rfc1123date&lt;/span&gt;&lt;br /&gt;        ContentLength = &lt;span class=&quot;token variable&quot;&gt;$contentLength&lt;/span&gt;&lt;br /&gt;        Method = &lt;span class=&quot;token variable&quot;&gt;$method&lt;/span&gt;&lt;br /&gt;        ContentType = &lt;span class=&quot;token variable&quot;&gt;$contentType&lt;/span&gt;&lt;br /&gt;        Resource = &lt;span class=&quot;token variable&quot;&gt;$resource&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$signature&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-LogAnalyticsSignature&lt;/span&gt; @signatureArguments&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$uri&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;https://&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$customerId&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;.ods.opinsights.azure.com&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$resource&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;?api-version=2016-04-01&quot;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$headers&lt;/span&gt; = @&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token string&quot;&gt;&quot;Authorization&quot;&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$signature&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token string&quot;&gt;&quot;Log-Type&quot;&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$logType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token string&quot;&gt;&quot;x-ms-date&quot;&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$rfc1123date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token string&quot;&gt;&quot;time-generated-field&quot;&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$TimeStampField&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$response&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Invoke-WebRequest&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Uri &lt;span class=&quot;token variable&quot;&gt;$uri&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Method &lt;span class=&quot;token variable&quot;&gt;$method&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ContentType &lt;span class=&quot;token variable&quot;&gt;$contentType&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Headers &lt;span class=&quot;token variable&quot;&gt;$headers&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Body &lt;span class=&quot;token variable&quot;&gt;$body&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;UseBasicParsing&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$response&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StatusCode&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
</description>
      <pubDate>Sun, 26 Aug 2018 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2018/08/getting-started-with-log-analytics-and-powershell-logging/</guid>
    </item>
    <item>
      <title>Troubleshooting a slow application with Application Insights</title>
      <link>https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/</link>
      <description>&lt;p&gt;&lt;a href=&quot;https://docs.microsoft.com/en-gb/azure/application-insights/app-insights-overview&quot;&gt;Application Insights (AppInsights)&lt;/a&gt; is a fantastic instrumentation framework that with minimal/zero configuration will start giving you rich data about your application&#39;s performance.&lt;/p&gt;
&lt;p&gt;We recently got some reports that one of our website solutions was &#39;slow&#39; when developing locally, and as much as we&#39;d like to turn to the DBA (which can often stand for Database Blamed Always...), with AppInsights we can be a little more rigorous.&lt;/p&gt;
&lt;p&gt;From our starting point of &#39;it runs slow locally, I think it is the database&#39; we&#39;ll figure out precisely how slow it is, and whether it really is the database or not.&lt;/p&gt;
&lt;h2 id=&quot;investigating-with-the-portal&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/#investigating-with-the-portal&quot;&gt;Investigating with the portal&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After navigating to the AppInsights resource that holds our development telemetry, we select the &lt;em&gt;performance&lt;/em&gt; blade.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We prefer to split our telemetry so that the production environment has its own resource, and that all DevTest resources go to their own resource.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We can now to navigate to the time period where the application was misbehaving (slow), and then filter out all of the roles and instances we don&#39;t care about.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/D3m8K4iIvp-292.avif 292w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/D3m8K4iIvp-292.webp 292w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Role Selection&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/D3m8K4iIvp-292.jpeg&quot; width=&quot;292&quot; height=&quot;309&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;In our case we drilled down to select the frontend website application, and the development machine which reported the problem.&lt;/p&gt;
&lt;p&gt;The performance blade gives an overview of the call count &amp;amp; duration of each endpoint (e.g. /Items/Detail, /Account/Login), and we can quickly see that a few endpoints had &lt;em&gt;really poor&lt;/em&gt; performance - 4 minutes to load one page. Discrepancies between production and local development are to be expected (none of our development team run any 64-core machines), but 4 minutes is pretty extreme.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/Of9HVUyCjc-295.avif 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/Of9HVUyCjc-295.webp 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Endpoints&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/Of9HVUyCjc-295.jpeg&quot; width=&quot;295&quot; height=&quot;224&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;These are all server-side response times. AppInsights also supports &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/application-insights/app-insights-javascript&quot;&gt;client side monitoring&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;From here we can either drill into that single slow call, or take a look at overall dependency performance.&lt;/p&gt;
&lt;h2 id=&quot;overall-dependencies&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/#overall-dependencies&quot;&gt;Overall dependencies&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;AppInsights will track every outbound dependency made by the application, giving us access to individual calls (e.g. this call at time T took 12ms) but also aggregate summaries.&lt;/p&gt;
&lt;p&gt;From the &lt;em&gt;dependencies&lt;/em&gt; view we see a breakdown of every outbound call with the call count and average duration. You&#39;ll need to know what &#39;normal&#39; looks like for your application, or at least have a rough idea of what to expect (volume of calls and expected response time). Looking at this view it was clear there was an issue with dependencies - call times I was expecting to be &amp;lt;10ms (in this environment) were running at almost 100ms.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/V5AELgbUD7-295.avif 295w, https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/V5AELgbUD7-590.avif 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/V5AELgbUD7-295.webp 295w, https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/V5AELgbUD7-590.webp 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/V5AELgbUD7-295.jpeg 295w, https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/V5AELgbUD7-590.jpeg 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Slow Calls&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/V5AELgbUD7-295.jpeg&quot; width=&quot;590&quot; height=&quot;416&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Flipping between a machine that wasn&#39;t suffering the problems and the problematic machine revealed the &#39;slow&#39; machine was almost 5-10x slower than the normal machine. You can be done via the portal (selecting a different instance), or by dropping into the &lt;a href=&quot;https://docs.microsoft.com/en-gb/azure/application-insights/app-insights-analytics&quot;&gt;Analytics&lt;/a&gt; portal and querying a few different machines:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let start=datetime(&amp;quot;2018-07-30T15:00:00.000Z&amp;quot;);
let end=datetime(&amp;quot;2018-07-30T17:00:00.000Z&amp;quot;);
dependencies
| where timestamp &amp;gt; start and timestamp &amp;lt; end
| where cloud_RoleInstance == &#39;SLOW_PC_HOSTNAME&#39;) // or &#39;FAST_PC_HOSTNAME&#39;
| where (cloud_RoleName == &#39;Website&#39;
| summarize count_=sum(itemCount), avg(duration), percentiles(duration,50,95) by target, name
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looking at averages as well as the median and 95th percentile shows us that this isn&#39;t some long-tail of slow requests pushing up the mean, but that all of our requests are slow. The problem is impacting all dependencies (e.g. GRPC), not just the database.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/0TzCr5Bmzy-295.avif 295w, https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/0TzCr5Bmzy-590.avif 590w, https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/0TzCr5Bmzy-885.avif 885w, https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/0TzCr5Bmzy-1180.avif 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/0TzCr5Bmzy-295.webp 295w, https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/0TzCr5Bmzy-590.webp 590w, https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/0TzCr5Bmzy-885.webp 885w, https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/0TzCr5Bmzy-1180.webp 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/0TzCr5Bmzy-295.jpeg 295w, https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/0TzCr5Bmzy-590.jpeg 590w, https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/0TzCr5Bmzy-885.jpeg 885w, https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/0TzCr5Bmzy-1180.jpeg 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Slow Calls in Analytics&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/0TzCr5Bmzy-295.jpeg&quot; width=&quot;1180&quot; height=&quot;299&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;AppInsights doesn&#39;t capture GRPC dependencies out of the box, but it does capture SQL and HttpClient calls.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;looking-at-an-individual-request&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/#looking-at-an-individual-request&quot;&gt;Looking at an individual request&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Another way to troubleshoot is to examine what happened in the context of a single request. If a dependency is called as part of a request, AppInsights will link the two together. By selecting one of the slow endpoints in the Overall view we can then see a list of all the sample operations AppInsights has captured.&lt;/p&gt;
&lt;p&gt;Clicking on any sample shows a timeline for the operation, including all the dependency calls associated with that operation. Picking a slow example (the 4 minute one) we can see again this isn&#39;t one slow dependency - they&#39;re &lt;em&gt;all&lt;/em&gt; slow (and what you can&#39;t see below is a long vertical scrollbar!).&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/4OoHMxuyT6-295.avif 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/4OoHMxuyT6-295.webp 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Slow Dependencies&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/4OoHMxuyT6-295.jpeg&quot; width=&quot;295&quot; height=&quot;225&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;AppInsights by default turns every request into an operation. If you&#39;re instrumenting a windows service you&#39;ll need to create and track your own operations if you want to see them show up in the Overall view with dependencies tagged to their parent operation. See more in the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/application-insights/application-insights-correlation&quot;&gt;Telemetry Correlation docs&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As with the overall view we can drop down to Analytics to query the raw data for a single operation:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dependencies
| where operation_Id == &amp;quot;oPerAtionId=&amp;quot;
| summarize sum(itemCount), avg(duration) by target, name
| order by sum_itemCount desc
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that we sum(itemCount) rather than count() because of &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/application-insights/app-insights-sampling&quot;&gt;automatic sampling&lt;/a&gt;. If there is no sampling this has no impact on your results, but if your data is being sampled and you don&#39;t sum(itemCount), your numbers will be off!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/RFNrb8BxKt-295.avif 295w, https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/RFNrb8BxKt-590.avif 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/RFNrb8BxKt-295.webp 295w, https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/RFNrb8BxKt-590.webp 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/RFNrb8BxKt-295.jpeg 295w, https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/RFNrb8BxKt-590.jpeg 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Slow Dependencies in Analytics&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/RFNrb8BxKt-295.jpeg&quot; width=&quot;590&quot; height=&quot;183&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;This analysis tells us the same story as the overall view - that all dependencies seem to be slow. This view additionally tells us that we might have an &lt;a href=&quot;https://www.brentozar.com/archive/2018/07/common-entity-framework-problems-n-1/&quot;&gt;N+1 problem&lt;/a&gt; somewhere due to the number of dependency calls being made with each operation.&lt;/p&gt;
&lt;h2 id=&quot;the-verdict&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/#the-verdict&quot;&gt;The verdict&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;AppInsights is fantastic for pinpointing issues, but it can&#39;t root cause for you (yet?). In this case I already had a pretty good idea of what the problem was, and we used AppInsights to confirm it.&lt;/p&gt;
&lt;p&gt;The slow development machine was located on the other side of the Atlantic from the development services it was configured to talk to! While some of our engineering team are working on increasing the speed of light, the short term fix we implemented was to target services local to the development machine.&lt;/p&gt;
&lt;p&gt;The examples above are just the tip of the iceberg when it comes to diagnosing application performance (whether through the portal, using workbooks, or via Analytics - you can quickly ask and get answers to some incredibly detailed questions).&lt;/p&gt;
&lt;p&gt;AppInsights is moving pretty quickly (every few months a new feature or experience pops up), and in the last 12 months we&#39;ve seen Log Analytics and AppInsights Analytics converge to support the &lt;a href=&quot;https://www.pluralsight.com/courses/kusto-query-language-kql-from-scratch&quot;&gt;Kusto Query Language (KQL)&lt;/a&gt;, and so it is worth keeping an eye on the &lt;a href=&quot;https://azure.microsoft.com/en-gb/blog/tag/application-insights/&quot;&gt;Application Insights Blog&lt;/a&gt;.&lt;/p&gt;
</description>
      <pubDate>Sun, 05 Aug 2018 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2018/08/troubleshooting-a-slow-application-with-application-insights/</guid>
    </item>
    <item>
      <title>Ensuring your Describe Tags are unique in Pester tests</title>
      <link>https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/</link>
      <description>&lt;p&gt;The name of each test in &lt;a href=&quot;https://github.com/taddison/SQLChecks&quot;&gt;SQLChecks&lt;/a&gt; is used as both the setting name in the configuration files, and to tag the Describe block. After seeing the benefit of fine-grained control over test execution (from Claudio Silva&#39;s post &lt;a href=&quot;https://claudioessilva.eu/2018/02/22/dbachecks-a-different-approach-for-an-in-progress-and-incremental-validation/&quot;&gt;dbachecks - a different approach...&lt;/a&gt;) this method of test invocation became the preferred way to leverage the SQLChecks library:&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$config&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Read-SqlChecksConfig&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Path &lt;span class=&quot;token variable&quot;&gt;$sqlChecksConfigPath&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$check&lt;/span&gt; in &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Get-SqlChecksFromConfig&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;# Note we invoke by -Tag $check - a test with no tag will never get invoked&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;Invoke-SqlChecks&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Config &lt;span class=&quot;token variable&quot;&gt;$config&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Tag &lt;span class=&quot;token variable&quot;&gt;$check&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There isn&#39;t yet a convention for how to name a test, and we&#39;ve already had some tests built with similar sounding names - it is only a matter of time before we get a duplicate. To prevent duplicate tests accidentally getting checked in (and causing unusual/broken behaviour for consumers), I recently added a test that parses the test files and ensures that each tag is not only unique within the file, but globally within SQLChecks.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/_78sjkIhB3-295.avif 295w, https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/_78sjkIhB3-590.avif 590w, https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/_78sjkIhB3-885.avif 885w, https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/_78sjkIhB3-1180.avif 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/_78sjkIhB3-295.webp 295w, https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/_78sjkIhB3-590.webp 590w, https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/_78sjkIhB3-885.webp 885w, https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/_78sjkIhB3-1180.webp 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/_78sjkIhB3-295.jpeg 295w, https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/_78sjkIhB3-590.jpeg 590w, https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/_78sjkIhB3-885.jpeg 885w, https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/_78sjkIhB3-1180.jpeg 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Describe tags test on AppVeyor&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/_78sjkIhB3-295.jpeg&quot; width=&quot;1180&quot; height=&quot;262&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;You can find the &lt;a href=&quot;https://github.com/taddison/SQLChecks/blob/master/tests/SQLChecks.Module.tests.ps1&quot;&gt;full test on GitHub&lt;/a&gt;, or read on for an explanation of how it is implemented. For a more thorough exploration of tests you can run on Describe blocks see SQLDBAWithABeard&#39;s blog &lt;a href=&quot;https://sqldbawithabeard.com/2018/01/15/using-the-ast-in-pester-for-dbachecks/&quot;&gt;Using the AST in Pester for dbachecks&lt;/a&gt;
(which inspired this test), or the TechNet post &lt;a href=&quot;https://blogs.technet.microsoft.com/heyscriptingguy/2012/09/26/learn-how-it-pros-can-use-the-powershell-ast/&quot;&gt;learn how it pros can use the PowerShell AST&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;overview&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/#overview&quot;&gt;Overview&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At a high level the test:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Finds all of the Pester test files that are shipped with the module&lt;/li&gt;
&lt;li&gt;Get the content of each file (we use &lt;code&gt;-Raw&lt;/code&gt; to get the full text, not a collection of lines)&lt;/li&gt;
&lt;li&gt;Parses each file so we can interact with them as objects (e.g. Function, Describe block, Operator) rather than strings of text&lt;/li&gt;
&lt;li&gt;Finds every Describe block&lt;/li&gt;
&lt;li&gt;Records the tag (if it exists) in an array of all tags&lt;/li&gt;
&lt;li&gt;Loops through the array of tags and checks if any of them has a count greater than 1&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Looping through the array and testing each tag gives the test a fairly useful output (&lt;code&gt;Tag X is a duplicate&lt;/code&gt;) rather than something generic and unhelpful (&lt;code&gt;There are duplicate tags&lt;/code&gt;). If your tests are going to fail you want them to be maximally helpful in debugging the failure.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Most of the requirements (get all the files, loop through the array) are fairly straightforward PowerShell - if we take out the parsing code the test looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;Describe &lt;span class=&quot;token string&quot;&gt;&quot;Module test Describe tags are unique&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token variable&quot;&gt;$tags&lt;/span&gt; = @&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;Get-ChildItem&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Filter&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tests&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ps1 &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Path &lt;span class=&quot;token variable&quot;&gt;$PSScriptRoot&lt;/span&gt;&#92;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&#92;src&#92;SQLChecks&#92;Tests &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; `&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;Get-Content&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Raw &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; `&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;ForEach-Object&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token comment&quot;&gt;#TODO: Add the tag to the array&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$tag&lt;/span&gt; in &lt;span class=&quot;token variable&quot;&gt;$tags&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      It &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$tag&lt;/span&gt; is a unique tag within the module&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$tags&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Where-Object&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$_&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-eq&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$tag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Count &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; Should Be 1&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;parsing-powershell&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/#parsing-powershell&quot;&gt;Parsing PowerShell&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Parsing PowerShell is a well-trodden path and we have some excellent tools available, with the &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.management.automation.language.parser?view=powershellsdk-1.1.0&quot;&gt;Parser class&lt;/a&gt; allowing us to take a string and turn it into an &lt;a href=&quot;https://en.wikipedia.org/wiki/Abstract_syntax_tree&quot;&gt;Abstract Syntax Tree (AST)&lt;/a&gt;. The tree structure gives us an incredibly rich object graph that we can interact with and query (so rather than writing a regex to find all Describe blocks, we can ask the AST to find all the Describe commands).&lt;/p&gt;
&lt;p&gt;We build the AST by passing the content from our files to the ParseInput command (this is on the inside of the &lt;code&gt;Get-Content -Raw | ForEachObject {&lt;/code&gt; block)&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$ast&lt;/span&gt; = &lt;span class=&quot;token namespace&quot;&gt;[Management.Automation.Language.Parser]&lt;/span&gt;::ParseInput&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$_&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token namespace&quot;&gt;[ref]&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token namespace&quot;&gt;[ref]&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$null&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;The two &lt;code&gt;[ref]$null&lt;/code&gt;s are needed to satisfy the required parameters of &lt;code&gt;ParseInput&lt;/code&gt; - in this case we don&#39;t care about capturing the tokens or any errors returned (see the &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/api/system.management.automation.language.parser.parseinput?view=powershellsdk-1.1.0&quot;&gt;ParseInput documentation&lt;/a&gt; for more details)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Once the AST is built we can then run a query to find all nodes that satisfy a set of predicates. In our case we want to find:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Every Describe command (Remember, Describe is a PowerShell function!)&lt;/li&gt;
&lt;li&gt;Where there is a second parameter (we&#39;ll assume the first parameter is the description, e.g. &lt;code&gt;Describe &amp;quot;Some Test&amp;quot;&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Where that second parameter name is Tag&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And once we have found every Describe command that satisfies those predicates, we want to take the fourth element (called the &lt;code&gt;CommandElement&lt;/code&gt;) which will be the Tag parameter&#39;s value. Translated into PowerShell our query looks like this (remember the &lt;code&gt;FindAll&lt;/code&gt; method can produce multiple results, so we have to extract the tag from each one):&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$ast&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;FindAll&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token keyword&quot;&gt;param&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$node&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token variable&quot;&gt;$node&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-is&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;[System.Management.Automation.Language.CommandAst]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-and&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token variable&quot;&gt;$node&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CommandElements&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;0&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Value &lt;span class=&quot;token operator&quot;&gt;-eq&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Describe&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-and&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token variable&quot;&gt;$node&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CommandElements&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;2&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-is&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;[System.Management.Automation.Language.CommandParameterAst]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-and&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token variable&quot;&gt;$node&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CommandElements&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;2&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ParameterName &lt;span class=&quot;token operator&quot;&gt;-eq&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Tag&quot;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;$true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ForEach-Object&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token variable&quot;&gt;$tags&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$_&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CommandElements&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;3&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Value&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The FindAll functions takes a predicate function which should return &lt;code&gt;$true&lt;/code&gt; if the node matches. The first line of our predicate (&lt;code&gt;...-is [System...&lt;/code&gt;) limits our search to commands only (not comments, blocks, etc.).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You&#39;ll note we don&#39;t check there is a fourth command element - this would be invalid syntax (missing parameter value/no test block) and that sounds like another test we could write.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;the-complete-test&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/#the-complete-test&quot;&gt;The complete test&lt;/a&gt;&lt;/h2&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;Describe &lt;span class=&quot;token string&quot;&gt;&quot;Module test Describe tags are unique&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token variable&quot;&gt;$tags&lt;/span&gt; = @&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;Get-ChildItem&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Filter&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tests&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ps1 &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Path &lt;span class=&quot;token variable&quot;&gt;$PSScriptRoot&lt;/span&gt;&#92;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&#92;src&#92;SQLChecks&#92;Tests &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Get-Content&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Raw &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ForEach-Object&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token variable&quot;&gt;$ast&lt;/span&gt; = &lt;span class=&quot;token namespace&quot;&gt;[Management.Automation.Language.Parser]&lt;/span&gt;::ParseInput&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$_&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;[ref]&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;[ref]&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token variable&quot;&gt;$ast&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;FindAll&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token keyword&quot;&gt;param&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$node&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token variable&quot;&gt;$node&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-is&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;[System.Management.Automation.Language.CommandAst]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-and&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token variable&quot;&gt;$node&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CommandElements&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;0&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Value &lt;span class=&quot;token operator&quot;&gt;-eq&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Describe&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-and&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token variable&quot;&gt;$node&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CommandElements&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;2&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-is&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;[System.Management.Automation.Language.CommandParameterAst]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-and&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token variable&quot;&gt;$node&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CommandElements&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;2&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ParameterName &lt;span class=&quot;token operator&quot;&gt;-eq&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Tag&quot;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;$true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ForEach-Object&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token variable&quot;&gt;$tags&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$_&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CommandElements&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;3&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Value&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$tag&lt;/span&gt; in &lt;span class=&quot;token variable&quot;&gt;$tags&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      It &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$tag&lt;/span&gt; is a unique tag within the module&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$tags&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Where-Object&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$_&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-eq&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$tag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Count &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; Should Be 1&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you&#39;ve not worked with an AST or parser before (or you have but not in PowerShell) some of this might look a little intimidating (it took me a while to grok it), but I&#39;d encourage you to persevere as the pattern is incredibly powerful.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It&#39;s pretty cool to write Pester tests for your Pester tests.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/q2JDE7kRcG-295.avif 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/q2JDE7kRcG-295.webp 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;I heard your like Pester tests&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/q2JDE7kRcG-295.jpeg&quot; width=&quot;295&quot; height=&quot;190&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h2 id=&quot;adding-more-tests&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/#adding-more-tests&quot;&gt;Adding more tests&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Once we have the pattern for parsing and inspecting a test we can add additional tests fairly easily - the below is a full example that will check if every test (&lt;code&gt;Describe&lt;/code&gt; block) has a &lt;code&gt;Tag&lt;/code&gt; parameter. The main difference to the previous test is we perform our checks per-describe (inside a context that specifies the file, so you can easily track down any failures). We&#39;ve also moved the tests for parameter/parameter value out of the &lt;code&gt;FindAll&lt;/code&gt; and onto the other side of a &lt;code&gt;Should&lt;/code&gt; test (because we want to find &lt;em&gt;all&lt;/em&gt; the describe blocks and check if they&#39;re well formed afterwards).&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;Describe &lt;span class=&quot;token string&quot;&gt;&quot;Every test has a tag&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;Get-ChildItem&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Filter&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tests&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ps1 &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Path &lt;span class=&quot;token variable&quot;&gt;$PSScriptRoot&lt;/span&gt;&#92;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&#92;src&#92;SQLChecks&#92;Tests &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ForEach-Object&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$content&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-Content&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$_&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;FullName &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Raw&lt;br /&gt;        Context &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$_&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token variable&quot;&gt;$ast&lt;/span&gt; = &lt;span class=&quot;token namespace&quot;&gt;[Management.Automation.Language.Parser]&lt;/span&gt;::ParseInput&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;[ref]&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;[ref]&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token variable&quot;&gt;$ast&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;FindAll&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token keyword&quot;&gt;param&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$node&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token variable&quot;&gt;$node&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-is&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;[System.Management.Automation.Language.CommandAst]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-and&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token variable&quot;&gt;$node&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CommandElements&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;0&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Value &lt;span class=&quot;token operator&quot;&gt;-eq&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Describe&quot;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;$true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ForEach-Object&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                It &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token function&quot;&gt;$&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$_&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CommandElements&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;1&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; has a tag&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                    &lt;span class=&quot;token variable&quot;&gt;$_&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CommandElements&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;2&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-is&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;[System.Management.Automation.Language.CommandParameterAst]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-and&lt;/span&gt;&lt;br /&gt;                        &lt;span class=&quot;token variable&quot;&gt;$_&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CommandElements&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;2&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ParameterName &lt;span class=&quot;token operator&quot;&gt;-eq&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Tag&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; Should Be &lt;span class=&quot;token boolean&quot;&gt;$true&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which looks something like this:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/z-EY-ZhAf0-295.avif 295w, https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/z-EY-ZhAf0-590.avif 590w, https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/z-EY-ZhAf0-885.avif 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/z-EY-ZhAf0-295.webp 295w, https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/z-EY-ZhAf0-590.webp 590w, https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/z-EY-ZhAf0-885.webp 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/z-EY-ZhAf0-295.jpeg 295w, https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/z-EY-ZhAf0-590.jpeg 590w, https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/z-EY-ZhAf0-885.jpeg 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Every test has a tag&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/z-EY-ZhAf0-295.jpeg&quot; width=&quot;885&quot; height=&quot;686&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
</description>
      <pubDate>Sat, 21 Jul 2018 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2018/07/ensuing-your-describe-tags-are-unique-in-pester-tests/</guid>
    </item>
    <item>
      <title>Adding Pester tests to a PowerShell module and scheduling CI with AppVeyor</title>
      <link>https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/</link>
      <description>&lt;p&gt;Adding Pester tests to a PowerShell module is probably one of the most valuable development activities you&#39;ll be able to perform, and I&#39;d encourage you to do it early in your project. I left it until rather late with SQLChecks, and as a result have broken the module several times.&lt;/p&gt;
&lt;p&gt;While some of breaks were definitely edge cases (and I don&#39;t think I have the the foresight to write a test that would have caught them), one of the most egregious errors caused SQLChecks to not export any functions at all. In this post I&#39;ll walk through the steps needed to do just that, which in brief are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a Pester test for the module&lt;/li&gt;
&lt;li&gt;Run the test locally, demonstrating that it will fail when the module doesn&#39;t export any functions&lt;/li&gt;
&lt;li&gt;Schedule the test to run automatically every time a commit is pushed to the &lt;a href=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/###&quot;&gt;GitHub repo&lt;/a&gt; (using &lt;a href=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/##&quot;&gt;AppVeyor&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Display the build status and number of passing tests on the Github readme&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And once we&#39;re done a quick glance at our readme will show:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/Tu9Nox7qi0-295.avif 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/Tu9Nox7qi0-295.webp 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;SQLChecks with badges&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/Tu9Nox7qi0-295.jpeg&quot; width=&quot;295&quot; height=&quot;123&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h2 id=&quot;adding-a-test&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/#adding-a-test&quot;&gt;Adding a Test&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We&#39;re going to add the Pester tests to a new &lt;code&gt;tests&lt;/code&gt; folder in the root of the repository. The module (including its manifest and all functions) are all in the &lt;code&gt;src/SQLChecks&lt;/code&gt; folder.&lt;/p&gt;
&lt;p&gt;The goal of our the first test is to ensure that the module exports &lt;em&gt;something&lt;/em&gt; - not a big hurdle, but it&#39;s a start. The complete &lt;code&gt;SQLChecks.Module.tests.ps1&lt;/code&gt; file looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;Import-Module&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$PSScriptRoot&lt;/span&gt;&#92;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&#92;src&#92;SQLChecks &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Force&lt;br /&gt;&lt;br /&gt;Describe &lt;span class=&quot;token string&quot;&gt;&quot;Import-Module SQLChecks&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  Context &lt;span class=&quot;token string&quot;&gt;&quot;Module Exports&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    It &lt;span class=&quot;token string&quot;&gt;&quot;Should export at least one function&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      @&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Get-Command&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Module SQLChecks&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Count &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; Should BeGreaterThan 0&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can then verify SQLChecks exports a function by running Invoke-Pester in the &lt;code&gt;tests&lt;/code&gt; folder:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/_kYTunovPp-295.avif 295w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/_kYTunovPp-590.avif 590w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/_kYTunovPp-885.avif 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/_kYTunovPp-295.webp 295w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/_kYTunovPp-590.webp 590w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/_kYTunovPp-885.webp 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/_kYTunovPp-295.jpeg 295w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/_kYTunovPp-590.jpeg 590w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/_kYTunovPp-885.jpeg 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Test success&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/_kYTunovPp-295.jpeg&quot; width=&quot;885&quot; height=&quot;273&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that because SQLChecks contains several &lt;code&gt;.tests.ps1&lt;/code&gt; files as embedded resources, running Invoke-Pester from the root folder of the repository will generate a lot of false positives.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;breaking-the-module&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/#breaking-the-module&quot;&gt;Breaking the module&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We can also demonstrate what happens when we break our module import process by commenting out the line that dot-sources all of our functions:&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$files&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-ChildItem&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Recurse &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Filter&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ps1 &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Path &lt;span class=&quot;token variable&quot;&gt;$PSScriptRoot&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;Functions&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$file&lt;/span&gt; in &lt;span class=&quot;token variable&quot;&gt;$files&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;#. $file.FullName # This line shouldn&#39;t be commented!&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which leads to...&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/JCchdTA2eZ-295.avif 295w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/JCchdTA2eZ-590.avif 590w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/JCchdTA2eZ-885.avif 885w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/JCchdTA2eZ-1180.avif 1180w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/JCchdTA2eZ-1475.avif 1475w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/JCchdTA2eZ-1770.avif 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/JCchdTA2eZ-295.webp 295w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/JCchdTA2eZ-590.webp 590w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/JCchdTA2eZ-885.webp 885w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/JCchdTA2eZ-1180.webp 1180w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/JCchdTA2eZ-1475.webp 1475w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/JCchdTA2eZ-1770.webp 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/JCchdTA2eZ-295.jpeg 295w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/JCchdTA2eZ-590.jpeg 590w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/JCchdTA2eZ-885.jpeg 885w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/JCchdTA2eZ-1180.jpeg 1180w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/JCchdTA2eZ-1475.jpeg 1475w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/JCchdTA2eZ-1770.jpeg 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Test failure&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/JCchdTA2eZ-295.jpeg&quot; width=&quot;1770&quot; height=&quot;505&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Although this is an extremely simple test we have created a repeatable and easy way to ensure (one form of) correctness. More importantly we&#39;ve also created a process which anyone contributing to SQLChecks can use to verify they&#39;ve not made things any worse.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Over time I plan to add more checks that will not only verify correctness, but also check formatting and conventions are being followed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now that we have a repeatable way of testing SQLChecks is correct, the next step is to automate that check...&lt;/p&gt;
&lt;h2 id=&quot;configuring-continuous-integration&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/#configuring-continuous-integration&quot;&gt;Configuring Continuous Integration&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;AppVeyor is a continuous integration &amp;amp; continuous deployment service that allows us to react to some or all commits to our repository. We&#39;re going to use it to run Pester tests after each commit, though if you browse through the &lt;a href=&quot;https://www.appveyor.com/docs/&quot;&gt;AppVeyor documentation&lt;/a&gt; you&#39;ll see it is capable of much more (build, test, and deploy software/artifacts).&lt;/p&gt;
&lt;p&gt;AppVeyor is free for open source projects, and if you don&#39;t already have an account you can &lt;a href=&quot;https://ci.appveyor.com/signup/free&quot;&gt;sign up for one with your GitHub credentials&lt;/a&gt;. From your account home you can create a new project and link it to your GitHub repository - AppVeyor will then start running its default build process every time you commit.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I&#39;m using GitHub, but AppVeyor also supports projects hosted in many other providers - Gitlab, VSTS, and Bitbucket to name a few.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Out of the box AppVeyor doesn&#39;t support automatic discovery of Pester tests, so we&#39;ll use an &lt;code&gt;appveyor.yml&lt;/code&gt; file (placed in the root of our repository) to tell it what pipeline to run after every commit:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Do nothing if all the files changed are markdown files (&lt;code&gt;.md&lt;/code&gt;) - see &lt;a href=&quot;https://www.appveyor.com/docs/how-to/filtering-commits/&quot;&gt;commit filtering&lt;/a&gt; for more details&lt;/li&gt;
&lt;li&gt;Ensure Pester is installed (using &lt;a href=&quot;https://chocolatey.org/&quot;&gt;Chocolatey&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Install modules listed as required in the SQLChecks manifest (&lt;a href=&quot;https://docs.microsoft.com/en-us/sql/powershell/download-sql-server-ps-module&quot;&gt;SqlServer&lt;/a&gt; and &lt;a href=&quot;https://dbatools.io/&quot;&gt;DBATools&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Don&#39;t run any build step (we have nothing to build)&lt;/li&gt;
&lt;li&gt;Run Invoke-Pester on the folder containing our tests&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;skip_commits&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;files&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;**/*.md&quot;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;install&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; cinst pester&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;ps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Install&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;Module dbatools &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;AllowClobber &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;Force&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;ps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Install&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;Module SqlServer &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;AllowClobber &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;Force&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; off&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;test_script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;ps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Invoke&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;Pester &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;Path .&#92;tests&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we trigger a build (either manually by clicking the &#39;New Build&#39; button inside your AppVeyor project, or commit a change to the repository) we&#39;ll see something like this:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/8gVppxiLdl-295.avif 295w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/8gVppxiLdl-590.avif 590w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/8gVppxiLdl-885.avif 885w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/8gVppxiLdl-1180.avif 1180w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/8gVppxiLdl-1475.avif 1475w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/8gVppxiLdl-1770.avif 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/8gVppxiLdl-295.webp 295w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/8gVppxiLdl-590.webp 590w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/8gVppxiLdl-885.webp 885w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/8gVppxiLdl-1180.webp 1180w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/8gVppxiLdl-1475.webp 1475w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/8gVppxiLdl-1770.webp 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/8gVppxiLdl-295.jpeg 295w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/8gVppxiLdl-590.jpeg 590w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/8gVppxiLdl-885.jpeg 885w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/8gVppxiLdl-1180.jpeg 1180w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/8gVppxiLdl-1475.jpeg 1475w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/8gVppxiLdl-1770.jpeg 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Build Success&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/8gVppxiLdl-295.jpeg&quot; width=&quot;1770&quot; height=&quot;1107&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h2 id=&quot;recording-test-results-in-appveyor&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/#recording-test-results-in-appveyor&quot;&gt;Recording test results in AppVeyor&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At this stage the build is still marked as successful even if some tests fail. Build status is dependent solely on the output of each step of the pipeline (if any fail, the build fails). Even if 100% of tests fail, the Invoke-Pester command still &lt;em&gt;completed successfully&lt;/em&gt;, and so the build passes. We need to let AppVeyor know one or more tests have failed by causing our test step to fail when tests fail. The &lt;a href=&quot;https://github.com/pester/Pester/wiki/Showing-Test-Results-in-CI-(TeamCity,-AppVeyor)&quot;&gt;Pester wiki&lt;/a&gt; has an example script we can use to do just that:&lt;/p&gt;
&lt;pre class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;test_script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;ps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;&lt;br /&gt;      $testResultsFile = &quot;.&#92;TestsResults.xml&quot;&lt;br /&gt;      $res = Invoke-Pester -OutputFormat NUnitXml -OutputFile $testResultsFile -PassThru&lt;br /&gt;      (New-Object &#39;System.Net.WebClient&#39;).UploadFile(&quot;https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)&quot;, (Resolve-Path $testResultsFile))&lt;br /&gt;      if ($res.FailedCount -gt 0) { &lt;br /&gt;          throw &quot;$($res.FailedCount) tests failed.&quot;&lt;br /&gt;      }&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By including a &lt;code&gt;throw&lt;/code&gt; in our script, we cause the script step to fail, so the whole build will be marked as failed.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/hnzDFiyBV7-295.avif 295w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/hnzDFiyBV7-590.avif 590w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/hnzDFiyBV7-885.avif 885w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/hnzDFiyBV7-1180.avif 1180w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/hnzDFiyBV7-1475.avif 1475w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/hnzDFiyBV7-1770.avif 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/hnzDFiyBV7-295.webp 295w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/hnzDFiyBV7-590.webp 590w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/hnzDFiyBV7-885.webp 885w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/hnzDFiyBV7-1180.webp 1180w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/hnzDFiyBV7-1475.webp 1475w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/hnzDFiyBV7-1770.webp 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/hnzDFiyBV7-295.jpeg 295w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/hnzDFiyBV7-590.jpeg 590w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/hnzDFiyBV7-885.jpeg 885w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/hnzDFiyBV7-1180.jpeg 1180w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/hnzDFiyBV7-1475.jpeg 1475w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/hnzDFiyBV7-1770.jpeg 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Test failed in AppVeyor&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/hnzDFiyBV7-295.jpeg&quot; width=&quot;1770&quot; height=&quot;345&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;The other part of this script (uploading a file with WebClient) takes the results and uploads them to AppVeyor, which means along with each build&#39;s status (pass/fail), we also get access to the results of each test. As you can see below, the &#39;Always Fail&#39; test did indeed fail:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/est0_mDvhA-295.avif 295w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/est0_mDvhA-590.avif 590w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/est0_mDvhA-885.avif 885w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/est0_mDvhA-1180.avif 1180w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/est0_mDvhA-1475.avif 1475w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/est0_mDvhA-1770.avif 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/est0_mDvhA-295.webp 295w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/est0_mDvhA-590.webp 590w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/est0_mDvhA-885.webp 885w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/est0_mDvhA-1180.webp 1180w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/est0_mDvhA-1475.webp 1475w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/est0_mDvhA-1770.webp 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/est0_mDvhA-295.jpeg 295w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/est0_mDvhA-590.jpeg 590w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/est0_mDvhA-885.jpeg 885w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/est0_mDvhA-1180.jpeg 1180w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/est0_mDvhA-1475.jpeg 1475w, https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/est0_mDvhA-1770.jpeg 1770w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Test results in AppVeyor&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/est0_mDvhA-295.jpeg&quot; width=&quot;1770&quot; height=&quot;382&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Now that we&#39;ve got the build status and tests working correctly, we can display them as badges using &lt;a href=&quot;https://shields.io/&quot;&gt;shields.io&lt;/a&gt; (and you can click the badge to view the most recent build/test results):&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ci.appveyor.com/project/taddison/sqlchecks&quot;&gt;&lt;img src=&quot;https://img.shields.io/appveyor/ci/taddison/SQLChecks.svg&quot; alt=&quot;Build Status&quot; /&gt;&lt;/a&gt;
&lt;a href=&quot;https://ci.appveyor.com/project/taddison/sqlchecks/build/tests&quot;&gt;&lt;img src=&quot;https://img.shields.io/appveyor/tests/taddison/SQLChecks.svg&quot; alt=&quot;Test Status&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you look at the URL you&#39;ll see how you can modify them to fit your own project (and if you check the main shields.io page you&#39;ll find they support a &lt;em&gt;lot&lt;/em&gt; of other badges too).&lt;/p&gt;
&lt;pre class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;&lt;span class=&quot;token url&quot;&gt;[&lt;span class=&quot;token content&quot;&gt;![Build Status&lt;/span&gt;](&lt;span class=&quot;token url&quot;&gt;https://img.shields.io/appveyor/ci/taddison/SQLChecks.svg&lt;/span&gt;)&lt;/span&gt;](https://ci.appveyor.com/project/taddison/sqlchecks)&lt;br /&gt;&lt;span class=&quot;token url&quot;&gt;[&lt;span class=&quot;token content&quot;&gt;![Test Status&lt;/span&gt;](&lt;span class=&quot;token url&quot;&gt;https://img.shields.io/appveyor/tests/taddison/SQLChecks.svg&lt;/span&gt;)&lt;/span&gt;](https://ci.appveyor.com/project/taddison/sqlchecks/build/tests)&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;next-steps&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/#next-steps&quot;&gt;Next Steps&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Once the CI process is in place it only gets more valuable as you increase the number of tests that can be executed for every commit. Some examples of how SQLChecks tests might be enhanced in the near future:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Check behaviour of commands without needing SQL Server by &lt;a href=&quot;https://github.com/pester/Pester/wiki/Mocking-with-Pester&quot;&gt;mocking certain cmdlets&lt;/a&gt; (e.g. &lt;code&gt;Invoke-SqlCmd&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Ensuring code standards are maintained with &lt;a href=&quot;https://blog.kilasuit.org/2016/03/29/invoking-psscriptanalyzer-in-pester-tests-for-each-rule/&quot;&gt;PSScriptAnalyzer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Making sure I don&#39;t &lt;a href=&quot;https://mattmcnabb.github.io/pester-testing-your-module-manifest&quot;&gt;screw up the module manifest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Ensuring that the &#39;Config&#39; family of commands all actually accept a Config parameter (see &lt;a href=&quot;https://github.com/taddison/SQLChecks/issues/16&quot;&gt;this GitHub issue&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And this is only scratching the surface - a lot of people have spent a lot of time testing pretty much anything with Pester and PowerShell (including things like SQL Server Infrastructure, perhaps with a module like SQLChecks...).&lt;/p&gt;
</description>
      <pubDate>Mon, 25 Jun 2018 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2018/06/adding-pester-tests-to-a-powershell-module-and-scheduling-ci-with-appveyor/</guid>
    </item>
    <item>
      <title>Setting sp_configure values with SQLChecks</title>
      <link>https://tjaddison.com/blog/2018/05/setting-sp-configure-values-with-sqlchecks/</link>
      <description>&lt;p&gt;As of v1.0 SQLChecks now contains the &lt;code&gt;Set-SpConfig&lt;/code&gt; command that allows you to take a file that documents a server configuration (specifically sp_configure values) and apply that configuration to a server. The configuration file is the same one used by Pester tests (&lt;a href=&quot;https://github.com/taddison/dbachecks-wrapper&quot;&gt;perhaps in combination with something like dbachecks&lt;/a&gt;), which means you now have a mechanism to document, test, and set your server&#39;s configuration.&lt;/p&gt;
&lt;p&gt;In order to apply the configuration to a single server you would run the following PowerShell (note that SQLChecks configuration files contain the instance name, which is why we don&#39;t have to specify a server):&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;  &lt;span class=&quot;token function&quot;&gt;Read-SqlChecksConfig&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;c:&#92;configs&#92;localhost.config.json&quot;&lt;/span&gt; `&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Set-SpConfig&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Verbose&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running in verbose means that it&#39;ll output progress as it changes a value, as well as a summary as it finishes (x/y config values updated).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note the command compares the configured value against the expected value - if the configured value is correct but the runtime value is wrong then this will neither fail the Pester tests, nor update the value when using &lt;code&gt;Set-SpConfig&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You can easily apply changes to a whole estate of servers by using the below script, which will find every config file in a folder (or subfolder) and apply the &lt;code&gt;sp_configure&lt;/code&gt; values to the servers.&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;Get-ChildItem&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Filter&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;json &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Recurse `&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Read-SqlChecksConfig&lt;/span&gt; `&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Set-SpConfig&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Verbose&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Under the hood this command leverages the &lt;a href=&quot;https://dbatools.io/&quot;&gt;dbatools&lt;/a&gt; library, specifically the commands &lt;a href=&quot;https://dbatools.io/functions/get-dbaspconfigure/&quot;&gt;Get-DbaSpConfigure&lt;/a&gt; and &lt;a href=&quot;https://dbatools.io/functions/set-dbaspconfigure/&quot;&gt;Set-DbaSpConfigure&lt;/a&gt;. These commands both end up creating SMO objects which are pretty costly in terms of time compared to a hand-rolled T-SQL solution to check/set these values.&lt;/p&gt;
&lt;p&gt;There is a &lt;a href=&quot;https://github.com/sqlcollaborative/dbachecks/issues/316&quot;&gt;GitHub issue around performance&lt;/a&gt;, and Microsoft have said to &#39;expect dramatic improvement in the coming months&#39; in &lt;a href=&quot;https://feedback.azure.com/forums/908035-sql-server/suggestions/33535612-smo-enumerations-slow-with-hundreds-of-databases&quot;&gt;this UserVoice issue related to SMO&lt;/a&gt;. If the performance improvements don&#39;t arrive or are not dramatic enough, adding some custom commands will probably be worth doing (especially if you are checking a lot of values over a lot of servers).&lt;/p&gt;
</description>
      <pubDate>Thu, 31 May 2018 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2018/05/setting-sp-configure-values-with-sqlchecks/</guid>
    </item>
    <item>
      <title>Cleaning up database mail profiles and accounts</title>
      <link>https://tjaddison.com/blog/2018/04/cleaning-up-database-mail-profiles-and-accounts/</link>
      <description>&lt;p&gt;You know that dev server you&#39;ve got lying around that has about half a dozen database mail profiles on? Maybe one account for every provider you&#39;ve tested? Or maybe it&#39;s even a production server that has had its profile faithfully updated to a new account each time your SMTP server moves but never had the old ones cleared out?&lt;/p&gt;
&lt;p&gt;After recently moving all of our servers to use SendGrid SMTP for sending out database mail I decided to perform some long overdue spring cleaning. In our case that included a couple of dev servers, as well as a production server from which you could divine the history of the company from the various database mail profiles and accounts it had.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Your servers might have a legitimate reason to contain multiple mail profiles/accounts, so only run the below script on a server if you are sure you want to remove every account/profile except the default.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@defaultProfileId&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@defaultProfileId&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;profile_id&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sysmail_principalprofile &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; pp&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; pp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;principal_sid &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0x0&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;/* Guest */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; pp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;is_default &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@defaultProfileId&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;throw &lt;span class=&quot;token number&quot;&gt;50000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;No default profile set&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;/* Delete non-default profiles and their account mappings */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;delete&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sysmail_profileaccount &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; profile_id &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&gt;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@defaultProfileId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;delete&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sysmail_profile &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; profile_id &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&gt;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@defaultProfileId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;/* Remove orphaned accounts */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt; cte &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;account_id&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sysmail_account &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; a&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;exists&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sysmail_profileaccount &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; pa&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; pa&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;account_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;account_id&lt;br /&gt;	&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;delete&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; cte&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For an example script which configures the default profile and account to use a SendGrid SMTP profile, see &lt;a href=&quot;https://gist.github.com/taddison/bad62ea292a395b1e86f967dd265f04f&quot;&gt;this gist&lt;/a&gt;. Plug in in your SendGrid &lt;a href=&quot;https://sendgrid.com/docs/Classroom/Send/How_Emails_Are_Sent/api_keys.html&quot;&gt;API key&lt;/a&gt;) and you&#39;re good to go.&lt;/p&gt;
</description>
      <pubDate>Tue, 24 Apr 2018 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2018/04/cleaning-up-database-mail-profiles-and-accounts/</guid>
    </item>
    <item>
      <title>Better expenses with Monzo and PowerShell</title>
      <link>https://tjaddison.com/blog/2018/04/better-expenses-with-monzo-and-powershell/</link>
      <description>&lt;p&gt;While making submitting expenses &lt;em&gt;fun&lt;/em&gt; is probably impossible, we can leverage Monzo and PowerShell to start automating away some of the manual steps involved. In my specific case I need to submit monthly expenses with receipt support and categorise each expense. Combine Monzo&#39;s tagging, image support, and CSV export with PowerShell&#39;s ability to...well do pretty much anything, and we have a workable solution!&lt;/p&gt;
&lt;p&gt;Assuming I&#39;ve correctly &lt;a href=&quot;https://monzo.com/blog/2018/04/12/transaction-tags/&quot;&gt;tagged all my transactions&lt;/a&gt; with the #expense tag I can then take the monthly export (available from the spending tab) and get my expense submission mostly prepared with a single PowerShell command:&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; c:&#92;src&#92;blog-monzo-expenses&#92;Expenses&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ps1&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$monzoExport&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;c:&#92;expenses&#92;monzoexports&#92;March2018.csv&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$exportFolder&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;c:&#92;expenses&#92;submissions&#92;March2018&quot;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Export-ExpenseFromMonzo&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;MonzoExport &lt;span class=&quot;token variable&quot;&gt;$monzoExport&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ExportFolder &lt;span class=&quot;token variable&quot;&gt;$exportFolder&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates a CSV that contains transactions tagged with #expense in the Monzo account currency, downloads the first image associated with each transaction (and names it to match the line in the CSV), and maps the Monzo category to the internal expense reporting category.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/04/better-expenses-with-monzo-and-powershell/wVoK_RqIAA-295.avif 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/04/better-expenses-with-monzo-and-powershell/wVoK_RqIAA-295.webp 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Expense Folder&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/04/better-expenses-with-monzo-and-powershell/wVoK_RqIAA-295.jpeg&quot; width=&quot;295&quot; height=&quot;105&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;A little copy-paste later and I&#39;m done. Read on for details on how it works, and where you can customise the data you return. If you want to use this on your own data (or try it out on some sample data) you can get everything you need in this &lt;a href=&quot;https://github.com/taddison/blog-monzo-expenses/blob/master/Expenses.ps1&quot;&gt;example GitHub repo&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;how-the-script-works&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/04/better-expenses-with-monzo-and-powershell/#how-the-script-works&quot;&gt;How the script works&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The script:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gets all the entries in the Monzo CSV export&lt;/li&gt;
&lt;li&gt;Filters out anything that doesn&#39;t contain the tag #expense&lt;/li&gt;
&lt;li&gt;Downloads the first image for each transaction (if one exists), and renames it to match the expense line (1,2,3...)&lt;/li&gt;
&lt;li&gt;Maps the Monzo category to an internal category&lt;/li&gt;
&lt;li&gt;Writes the expense line to a CSV, including ID, Date, Currency, Amount, Category, and if a receipt is present&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&#39;s go through some of those steps in more detail. First of all - importing the CSV and filtering. A tag in Monzo is stored in the notes field.&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$entries&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Import-Csv&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Path &lt;span class=&quot;token variable&quot;&gt;$MonzoExport&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$expenseEntries&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$entries&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Where-Object&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$_&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;notes &lt;span class=&quot;token operator&quot;&gt;-like&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;*#expense*&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We then iterate over every entry and first of all check to see if there is an image. The Monzo format exports the images in an array in the form [ImageUrl,Image2URL]. We only grab the first image if there is more than one, and save it to disk.&lt;/p&gt;
&lt;p&gt;The images are uploaded to S3 buckets with no extension, so we use the content-type to map to the appropriate extension (e.g. &lt;code&gt;image/jpeg&lt;/code&gt; -&amp;gt; &lt;code&gt;.jpg&lt;/code&gt;). While testing I&#39;ve used images hosted in GitHub, which don&#39;t return a content-type - as such I&#39;ve added &lt;code&gt;.jpg&lt;/code&gt;, which always gets a photo viewer to launch.&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$hasReceipt&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;No&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$imagePathWithoutExtension&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$exportFolder&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&#92;Receipt_&lt;span class=&quot;token variable&quot;&gt;$expenseId&lt;/span&gt;&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$expense&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;receipt &lt;span class=&quot;token operator&quot;&gt;-match&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&#92;[([^,]+).*&#92;]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$expenseImage&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Invoke-WebRequest&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Uri &lt;span class=&quot;token variable&quot;&gt;$Matches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;1&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;OutFile &lt;span class=&quot;token variable&quot;&gt;$imagePathWithoutExtension&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;PassThru&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$extension&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-ExtensionFromContentType&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ContentType &lt;span class=&quot;token variable&quot;&gt;$expenseImage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Content-Type&#39;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$extension&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-eq&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;# It&#39;ll get a photo viewer to launch&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$extension&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;jpg&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;Rename-Item&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Path &lt;span class=&quot;token variable&quot;&gt;$imagePathWithoutExtension&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;NewName &lt;span class=&quot;token string&quot;&gt;&quot;Receipt_&lt;span class=&quot;token variable&quot;&gt;$expenseId&lt;/span&gt;.&lt;span class=&quot;token variable&quot;&gt;$extension&lt;/span&gt;&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$hasReceipt&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;Yes&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We then add an expense object to an array we&#39;ll later export. Whatever properties this object has will end up in the CSV, so if you wanted to add extra data (e.g. the whole notes, the merchant name) this is where you&#39;d do it.&lt;/p&gt;
&lt;p&gt;Note that we multiply the amount by -1, as debits (charges) show up as negative amounts in the transaction feed, whereas you&#39;ll typically report expenses as a positive amount.&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$outExpenses&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;[pscustomobject]&lt;/span&gt;@&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    Id = &lt;span class=&quot;token variable&quot;&gt;$expenseId&lt;/span&gt;&lt;br /&gt;    Date = &lt;span class=&quot;token namespace&quot;&gt;[DateTime]&lt;/span&gt;::Parse&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$expense&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;created&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ToString&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;yyyy-MM-dd&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;Type&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-ExpenseTypeFromCategory&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$expense&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;category&lt;br /&gt;    Currency = &lt;span class=&quot;token variable&quot;&gt;$expense&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currency&lt;br /&gt;    Amount = &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token namespace&quot;&gt;[double]&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$expense&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;amount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;1&lt;br /&gt;    HasReceipt = &lt;span class=&quot;token variable&quot;&gt;$hasReceipt&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally we export the CSV into the folder we&#39;ve been saving the images into.&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$outExpenses&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Export-Csv&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Path &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$exportFolder&lt;/span&gt;&#92;Expenses.csv&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;NoTypeInformation&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;using-this-on-your-data&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/04/better-expenses-with-monzo-and-powershell/#using-this-on-your-data&quot;&gt;Using this on your data&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The only thing you need to get started with your own data is the &lt;code&gt;Expenses.ps1&lt;/code&gt; file from the sample repo. Modify the PowerShell script below to include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The path to your Expenses.ps1 file&lt;/li&gt;
&lt;li&gt;The path to your Monzo CSV&lt;/li&gt;
&lt;li&gt;The folder you want to save the export in&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt; c:&#92;path&#92;to&#92;Expenses&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ps1&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$monzoExport&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;c:&#92;src&#92;blog-monzo-expenses&#92;Example&#92;March2018Export.csv&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$exportFolder&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;c:&#92;temp&#92;expenses&#92;March2018&quot;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Export-ExpenseFromMonzo&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;MonzoExport &lt;span class=&quot;token variable&quot;&gt;$monzoExport&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ExportFolder &lt;span class=&quot;token variable&quot;&gt;$exportFolder&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hopefully you can see how you can easily take the general idea and customise it to fit whatever process you follow. Some examples of things I&#39;ve played around with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tags to delineate trips (e.g. #tripNYC)&lt;/li&gt;
&lt;li&gt;Tags to extract extra metadata (e.g. #E-Client-12345)&lt;/li&gt;
&lt;li&gt;Caps (e.g. don&#39;t expense more than $50 in food/day)&lt;/li&gt;
&lt;li&gt;Time zone offsets ()&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;appendix-sample-monzo-export&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/04/better-expenses-with-monzo-and-powershell/#appendix-sample-monzo-export&quot;&gt;Appendix: Sample Monzo export&lt;/a&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;id,created,amount,currency,local_amount,,local_currency,category,emoji,description,address,notes,receipt
tx_00001ABCDEFGHIJKLMNOPQ,2018-03-30 17:32:57 +0000,-25,GBP,-25,1,GBP,general,,,,,
tx_00002ABCDEFGHIJKLMNOPQ,2018-03-29 19:33:45 +0000,-20,GBP,-20,1,GBP,general,,,,,
tx_00003ABCDEFGHIJKLMNOPQ,2018-03-29 19:33:06 +0000,-13.3,GBP,-13.3,1,GBP,general,,,,,
tx_00004ABCDEFGHIJKLMNOPQ,2018-03-24 15:21:47 +0000,-5.5,GBP,-5.5,1,GBP,general,,,,,
tx_00005ABCDEFGHIJKLMNOPQ,2018-03-17 11:59:09 +0000,-19.5,GBP,-19.5,1,GBP,groceries,,,,#expense,
tx_00006ABCDEFGHIJKLMNOPQ,2018-03-11 16:11:55 +0000,-67.99,GBP,-67.99,1,GBP,entertainment,,,,#expense,
tx_00007ABCDEFGHIJKLMNOPQ,2018-03-10 01:03:26 +0000,-5.28,GBP,-7.27,1.376893939,USD,shopping,,,,#expense,[https://github.com/taddison/blog-monzo-expenses/raw/master/Example/2327734421_7087f8f47e_z]
&lt;/code&gt;&lt;/pre&gt;
</description>
      <pubDate>Sun, 22 Apr 2018 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2018/04/better-expenses-with-monzo-and-powershell/</guid>
    </item>
    <item>
      <title>Improving database Pester tests - per-database checks</title>
      <link>https://tjaddison.com/blog/2018/04/improving-database-pester-tests-per-database-checks/</link>
      <description>&lt;p&gt;When we first started putting tests together for &lt;a href=&quot;https://github.com/taddison/SQLChecks&quot;&gt;SQLChecks&lt;/a&gt; we naively/optimistically thought we&#39;d mostly be seeing a sea of green, with failures being rare. This influenced the way we developed &#39;database&#39; tests, so that when you test an instance for &#39;databases with files too full&#39;, the test gives you a pass/fail for the entire instance.&lt;/p&gt;
&lt;p&gt;This is fine when the test passes, but as soon as it fails it is spectacularly unhelpful in figuring out what broke.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/04/improving-database-pester-tests-per-database-checks/nhe-c2c9-u-295.avif 295w, https://tjaddison.com/blog/2018/04/improving-database-pester-tests-per-database-checks/nhe-c2c9-u-590.avif 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/04/improving-database-pester-tests-per-database-checks/nhe-c2c9-u-295.webp 295w, https://tjaddison.com/blog/2018/04/improving-database-pester-tests-per-database-checks/nhe-c2c9-u-590.webp 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2018/04/improving-database-pester-tests-per-database-checks/nhe-c2c9-u-295.jpeg 295w, https://tjaddison.com/blog/2018/04/improving-database-pester-tests-per-database-checks/nhe-c2c9-u-590.jpeg 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Something is wrong&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/04/improving-database-pester-tests-per-database-checks/nhe-c2c9-u-295.jpeg&quot; width=&quot;590&quot; height=&quot;143&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Arriving in the morning to discover one (or more) databases on an instance have a problem isn&#39;t particularly actionable, and so I&#39;ve recently started to move all SQLChecks tests over to per-database, which is a lot more helpful.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/04/improving-database-pester-tests-per-database-checks/Pw9304b9G5-295.avif 295w, https://tjaddison.com/blog/2018/04/improving-database-pester-tests-per-database-checks/Pw9304b9G5-590.avif 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/04/improving-database-pester-tests-per-database-checks/Pw9304b9G5-295.webp 295w, https://tjaddison.com/blog/2018/04/improving-database-pester-tests-per-database-checks/Pw9304b9G5-590.webp 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2018/04/improving-database-pester-tests-per-database-checks/Pw9304b9G5-295.jpeg 295w, https://tjaddison.com/blog/2018/04/improving-database-pester-tests-per-database-checks/Pw9304b9G5-590.jpeg 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Some specific database is wrong&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/04/improving-database-pester-tests-per-database-checks/Pw9304b9G5-295.jpeg&quot; width=&quot;590&quot; height=&quot;218&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;The rest of this post covers what the changes looked like, and talk a little more about the benefits of structuring tests this way.&lt;/p&gt;
&lt;h2 id=&quot;test-changes&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/04/improving-database-pester-tests-per-database-checks/#test-changes&quot;&gt;Test changes&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The original test is shown below, which produces a single result for the whole instance.&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;Describe &lt;span class=&quot;token string&quot;&gt;&quot;Data file space used&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Tag MaxDataFileSize &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$config&lt;/span&gt; in &lt;span class=&quot;token variable&quot;&gt;$configs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$serverInstance&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ServerInstance&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$MaxDataFileParams&lt;/span&gt;=@&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            ServerInstance = &lt;span class=&quot;token variable&quot;&gt;$serverInstance&lt;/span&gt;&lt;br /&gt;            MaxDataFileSpaceUsedPercent = &lt;span class=&quot;token variable&quot;&gt;$maxDataConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SpaceUsedPercent&lt;br /&gt;            WhiteListFiles = &lt;span class=&quot;token variable&quot;&gt;$maxDataConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;WhitelistFiles&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        Context &lt;span class=&quot;token string&quot;&gt;&quot;Testing for data file space usage on &lt;span class=&quot;token variable&quot;&gt;$serverInstance&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            It &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$serverInstance&lt;/span&gt; has all databases under Max DataFile Space Used&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                @&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Get-DatabasesOverMaxDataFileSpaceUsed&lt;/span&gt; @MaxDataFileParams&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Count &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; Should Be 0&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In order to move the test to report per-database:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;Get-DatabasesOverMaxDataFileSpaceUsed&lt;/code&gt; function is replaced with a new one that no longer iterates over all databases in the SQL query, and instead executes against a single database, returning all files that are larger than the space used configuration value&lt;/li&gt;
&lt;li&gt;A list of database to run the check against is obtained by using &lt;code&gt;Get-DatabasesToCheck&lt;/code&gt;, ignoring any replica databases&lt;/li&gt;
&lt;li&gt;A new test (It) is executed for each database - note the database name is updated in the hashtable each iteration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The formatting of the Context/It block was also changed to support easier consumption in the &lt;a href=&quot;https://github.com/sqlcollaborative/dbachecks&quot;&gt;dbachecks&lt;/a&gt; Power BI dashboard (which expects instance name to be in specific places).&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;Describe &lt;span class=&quot;token string&quot;&gt;&quot;Data file space used&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Tag MaxDataFileSize &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$config&lt;/span&gt; in &lt;span class=&quot;token variable&quot;&gt;$configs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$serverInstance&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ServerInstance&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$spaceUsedPercentLimit&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$maxDataConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SpaceUsedPercent&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$MaxDataFileParams&lt;/span&gt;=@&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            ServerInstance = &lt;span class=&quot;token variable&quot;&gt;$serverInstance&lt;/span&gt;&lt;br /&gt;            MaxDataFileSpaceUsedPercent = &lt;span class=&quot;token variable&quot;&gt;$spaceUsedPercentLimit&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token variable&quot;&gt;$databases&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-DatabasesToCheck&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ServerInstance &lt;span class=&quot;token variable&quot;&gt;$serverInstance&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;PrimaryOnly&lt;br /&gt;&lt;br /&gt;        Context &lt;span class=&quot;token string&quot;&gt;&quot;Testing for data file space usage on &lt;span class=&quot;token variable&quot;&gt;$serverInstance&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$database&lt;/span&gt; in &lt;span class=&quot;token variable&quot;&gt;$databases&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                It &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$database&lt;/span&gt; files are all under &lt;span class=&quot;token variable&quot;&gt;$spaceUsedPercentLimit&lt;/span&gt;% full on &lt;span class=&quot;token variable&quot;&gt;$serverInstance&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                    &lt;span class=&quot;token variable&quot;&gt;$MaxDataFileParams&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Database = &lt;span class=&quot;token variable&quot;&gt;$database&lt;/span&gt;&lt;br /&gt;                    @&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Get-DatabaseFilesOverMaxDataFileSpaceUsed&lt;/span&gt; @MaxDataFileParams&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Count &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; Should &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Be 0&lt;br /&gt;                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that these tests have had code removed that isn&#39;t relevant to this example - you can see the full test in the &lt;a href=&quot;https://github.com/taddison/archived.SQLChecks/blob/master/src/SQLChecks/Tests/Database.tests.ps1&quot;&gt;GitHub source&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;appendix-test-code&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/04/improving-database-pester-tests-per-database-checks/#appendix-test-code&quot;&gt;Appendix: Test code&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The script to run a single test is shown below, and has an ad-hoc config built to test no database file on localhost is more than 90 percent full.&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;Import-Module&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&#92;src&#92;SQLChecks &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Force&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$configData&lt;/span&gt; = @&lt;span class=&quot;token string&quot;&gt;&#39;&lt;br /&gt;{&lt;br /&gt;    &quot;ServerInstance&quot;: &quot;localhost&quot;&lt;br /&gt;    ,&quot;MaxDataFileSize&quot;: {&lt;br /&gt;        &quot;SpaceUsedPercent&quot;: 90&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&#39;&lt;/span&gt;@&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$config&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$configData&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ConvertFrom-Json&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Invoke-SqlChecks&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Config &lt;span class=&quot;token variable&quot;&gt;$config&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Tag MaxDataFileSize&lt;/code&gt;&lt;/pre&gt;
</description>
      <pubDate>Sun, 08 Apr 2018 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2018/04/improving-database-pester-tests-per-database-checks/</guid>
    </item>
    <item>
      <title>Pattern - SQL Server as a shared cache for expensive stored procedures</title>
      <link>https://tjaddison.com/blog/2018/03/pattern-sql-server-as-a-shared-cache-for-expensive-stored-procedures/</link>
      <description>&lt;p&gt;The following scaling rules will take you a long way if you are supporting an environment with a SQL Server datastore:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cache as much as you can in the application tier&lt;/li&gt;
&lt;li&gt;Offload as much computation into the application as possible&lt;/li&gt;
&lt;li&gt;Minimise the work high-volume queries have to do&lt;/li&gt;
&lt;li&gt;Limit the number of transactions you do, and the work each transaction does&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Knowing when to ignore (or even break) these rules is what keeps the job interesting.&lt;/p&gt;
&lt;p&gt;The rest of the post will walk through a generic pattern to cache stored procedure results that vary by parameter, including the logic needed to expire, clean up, and evaluate the cache. This solution has been battle tested in production with a fairly expensive procedure called concurrently from multiple application nodes (for dozens of different parameter combinations).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The environment that motivated this work already aggressively caches results in the application tier - the specific motivation to cache the results in SQL came from the number of application nodes increasing. Building a shared cache service [or introducing something like Redis/Memcached] is a non-trivial engineering project, and the SQL CPU pressure this proc caused was significant. Other options (e.g. incremental cache updates/aggregation in the application tier) were also judged to be significant projects (or at least, more significant than caching it in the database!).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/03/pattern-sql-server-as-a-shared-cache-for-expensive-stored-procedures/75Lx1_A9tV-295.avif 295w, https://tjaddison.com/blog/2018/03/pattern-sql-server-as-a-shared-cache-for-expensive-stored-procedures/75Lx1_A9tV-590.avif 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/03/pattern-sql-server-as-a-shared-cache-for-expensive-stored-procedures/75Lx1_A9tV-295.webp 295w, https://tjaddison.com/blog/2018/03/pattern-sql-server-as-a-shared-cache-for-expensive-stored-procedures/75Lx1_A9tV-590.webp 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2018/03/pattern-sql-server-as-a-shared-cache-for-expensive-stored-procedures/75Lx1_A9tV-295.jpeg 295w, https://tjaddison.com/blog/2018/03/pattern-sql-server-as-a-shared-cache-for-expensive-stored-procedures/75Lx1_A9tV-590.jpeg 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Cache Control&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/03/pattern-sql-server-as-a-shared-cache-for-expensive-stored-procedures/75Lx1_A9tV-295.jpeg&quot; width=&quot;590&quot; height=&quot;393&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;If you want to see the full example, you can check the &lt;a href=&quot;https://github.com/taddison/DBCacheExample&quot;&gt;complete source on GitHub&lt;/a&gt;. Before deploying into production I strongly suggest reading through the entire post for caveats and trade-offs.&lt;/p&gt;
&lt;h2 id=&quot;the-problem&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/03/pattern-sql-server-as-a-shared-cache-for-expensive-stored-procedures/#the-problem&quot;&gt;The problem&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For this worked example we&#39;re going to use a procedure that gets the top selling products for a given category - something like Amazon&#39;s category page which shows the top sellers.&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;procedure&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;GetTopSellingProducts&lt;br /&gt;	&lt;span class=&quot;token variable&quot;&gt;@categoryId&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt;	&lt;span class=&quot;token keyword&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;			p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ProductId&lt;br /&gt;			&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TotalSalePrice&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; TotalSalePrice&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt;	dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Sales &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; s&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;join&lt;/span&gt;	dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Product &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; p&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; 		p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ProductId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; s&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ProductId&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt;	p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CategoryId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@categoryId&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt;		s&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SaleDateTime &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; getutcdate&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt;		p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AvailableStock &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;by&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ProductId&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TotalSalePrice&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While this procedure might only take 2 seconds to run (and be subsequently cached for a few minutes), we&#39;re potentially calling it _categories _ nodes* times per cache duration. If we assume our cache duration is 5 minutes, and we have 100 categories and 10 nodes, that comes to 400 CPU-seconds per-minute, 10x what it could be.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In a perfect world deploying this solution would always reduce the time spent by a factor of &lt;code&gt;number of nodes&lt;/code&gt;. The time spent on managing the cache is negligible, and the places where you lose perfect scaling tend to come from concurrent calls - this was an explicit design decision where preferring the proc to execute and cache multiple copies of the result set was preferable to a solution that uses locking to ensure only one session could update the cache at a time.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;cache-solution&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/03/pattern-sql-server-as-a-shared-cache-for-expensive-stored-procedures/#cache-solution&quot;&gt;Cache solution&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The solution comprises of a few different pieces:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A master control table to toggle caching on/off&lt;/li&gt;
&lt;li&gt;A control table per-proc to keep track of what is available in the cache&lt;/li&gt;
&lt;li&gt;A results table per-proc which acts as the cache&lt;/li&gt;
&lt;li&gt;A clean-up procedure, which can be used to periodically clean the results&lt;/li&gt;
&lt;li&gt;The stored procedure itself, which is augmented with caching logic&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The reasons why there exist control tables as well as results tables are discussed in more depth in the Tradeoffs and design decisions section below.&lt;/p&gt;
&lt;h3 id=&quot;tables&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/03/pattern-sql-server-as-a-shared-cache-for-expensive-stored-procedures/#tables&quot;&gt;Tables&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;schema&lt;/span&gt; DBCache &lt;span class=&quot;token keyword&quot;&gt;authorization&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;go&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;table&lt;/span&gt; DBCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MasterControl&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;	CachedEntity &lt;span class=&quot;token keyword&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;IsEnabled &lt;span class=&quot;token keyword&quot;&gt;bit&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;constraint&lt;/span&gt; PK_MasterControl&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;clustered&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;CachedEntity&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;go&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; sequence DBCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SEQ_Control &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;start&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;br /&gt;	increment &lt;span class=&quot;token keyword&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;cycle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;go&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;table&lt;/span&gt; DBCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbo_GetTopSellingProducts_Control&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;	CacheDateTime datetime2&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;ExpiryDateTime datetime2&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;Param_CategoryId &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;UseCount &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;Id &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;constraint&lt;/span&gt; PK_dbo_GetTopSellingProducts_Control&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;primary&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;clustered&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;index&lt;/span&gt; IX_dbo_GetTopSellingProducts_Control_ExpiryParams&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;nonclustered&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Param_CategoryId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ExpiryDateTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;go&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;table&lt;/span&gt; DBCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbo_GetTopSellingProducts_Results&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;	ControlId &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;ProductId &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;TotalSalePrice money &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;index&lt;/span&gt; CIX_dbo_GetTopSellingProducts_Results&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;clustered&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ControlId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;go&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each stored procedure that needs to be cached has a control/result table that are unique to that stored procedure. Specifically, we capture all parameters in the control table, and all output columns in the results table. The control table must be indexed based on both the parameters of the procedure and the expiry date to support efficient lookups (answering the question &#39;is there a valid cached result for my parameter set?&#39;).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Ensure the types and nullability of these columns match what the stored procedure already uses/produces. A great way to get the type information of the result set is to use &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-describe-first-result-set-transact-sql&quot;&gt;sp_describe_first_result_set&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;stored-procedure&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/03/pattern-sql-server-as-a-shared-cache-for-expensive-stored-procedures/#stored-procedure&quot;&gt;Stored Procedure&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The stored procedure shown above changes drastically when we add caching. The complexity tradeoffs are discussed in more detail in the sections below.&lt;/p&gt;
&lt;p&gt;The generalised stored procedure flow for a procedure that has caching looks something like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lookup the caching status from the master control table (on/off)&lt;/li&gt;
&lt;li&gt;If caching is enabled, check to see if there is a valid cache entry (same parameters, expiry date in the future)
&lt;ul&gt;
&lt;li&gt;If that record exists, return the results from the cache table&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;If the cached record doesn&#39;t exist, run the procedure logic and capture the final results in a temporary table&lt;/li&gt;
&lt;li&gt;If caching is enabled, attempt to populate the cache
&lt;ul&gt;
&lt;li&gt;First of all insert the results from the temp table&lt;/li&gt;
&lt;li&gt;Insert the control record and set the expiry date&lt;/li&gt;
&lt;li&gt;Keep a record of the newly inserted cache Id&lt;/li&gt;
&lt;li&gt;If any errors are hit, null out the variable holding cache Id&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;If the cache Id isn&#39;t null, serve results from the cache&lt;/li&gt;
&lt;li&gt;If the cache Id is null, serve results from the temp table&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;This procedure features an order by, and so the order by has to be repeated everywhere results are returned to the client.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;alter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;procedure&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;GetTopSellingProducts&lt;br /&gt;	&lt;span class=&quot;token variable&quot;&gt;@categoryId&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; nocount &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@DB_CACHE_STATUS&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;bit&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@CACHE_DURATION_MINUTES&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt;	&lt;span class=&quot;token variable&quot;&gt;@DB_CACHE_STATUS&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; mc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;IsEnabled&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt;	DBCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MasterControl &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; mc&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt;	mc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CachedEntity &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;dbo.GetTopSellingProducts&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@cacheControlId&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@DB_CACHE_STATUS&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@cacheControlId&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; c&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Id&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; DBCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbo_GetTopSellingProducts_Control &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; c&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt;	c&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ExpiryDateTime &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; getutcdate&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt;		c&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Param_CategoryId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@categoryId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;update&lt;/span&gt; DBCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbo_GetTopSellingProducts_Control&lt;br /&gt;			&lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; UseCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; UseCount &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; Id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@cacheControlId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@cacheControlId&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ProductId&lt;br /&gt;			  &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TotalSalePrice&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; DBCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbo_GetTopSellingProducts_Results &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; r&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt;	r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ControlId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@cacheControlId&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;by&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TotalSalePrice &lt;span class=&quot;token keyword&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt;	&lt;span class=&quot;token keyword&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;			p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ProductId&lt;br /&gt;			&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TotalSalePrice&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; TotalSalePrice&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;into&lt;/span&gt;	&lt;span class=&quot;token comment&quot;&gt;#results&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt;	dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Sales &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; s&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;join&lt;/span&gt;	dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Product &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; p&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; 		p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ProductId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; s&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ProductId&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt;	p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CategoryId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@categoryId&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt;		s&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SaleDateTime &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; getutcdate&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt;		p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AvailableStock &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;by&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ProductId&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;s&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TotalSalePrice&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@cacheControlId&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@DB_CACHE_STATUS&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt; try&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@cacheControlId&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;next&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; DBCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SEQ_Control&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;insert&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;into&lt;/span&gt; DBCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbo_GetTopSellingProducts_Results&lt;br /&gt;		&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;			ControlId&lt;br /&gt;			&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;ProductId&lt;br /&gt;			&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;TotalSalePrice&lt;br /&gt;		&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt;	&lt;span class=&quot;token variable&quot;&gt;@cacheControlId&lt;/span&gt;&lt;br /&gt;			   &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ProductId&lt;br /&gt;			   &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TotalSalePrice&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt;	&lt;span class=&quot;token comment&quot;&gt;#results as r;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;insert&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;into&lt;/span&gt; DBCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbo_GetTopSellingProducts_Control&lt;br /&gt;		&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;			Id&lt;br /&gt;			&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;CacheDateTime&lt;br /&gt;			&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;ExpiryDateTime&lt;br /&gt;			&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;Param_CategoryId&lt;br /&gt;			&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;UseCount&lt;br /&gt;		&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;values&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;token variable&quot;&gt;@cacheControlId&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;getutcdate&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;dateadd&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;minute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@CACHE_DURATION_MINUTES&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;getutcdate&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@categoryId&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt; try&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt; catch&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@cacheControlId&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt; catch&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@cacheControlId&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ProductId&lt;br /&gt;			  &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TotalSalePrice&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; DBCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbo_GetTopSellingProducts_Results &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; r&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt;	r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ControlId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@cacheControlId&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;by&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TotalSalePrice &lt;span class=&quot;token keyword&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ProductId&lt;br /&gt;			  &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TotalSalePrice&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;#results as r&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;by&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TotalSalePrice &lt;span class=&quot;token keyword&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;clean-up-job&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/03/pattern-sql-server-as-a-shared-cache-for-expensive-stored-procedures/#clean-up-job&quot;&gt;Clean-up job&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The clean-up job implements the following logic:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Disable caching for the stored procedure in the master control table&lt;/li&gt;
&lt;li&gt;Wait for 45 seconds&lt;/li&gt;
&lt;li&gt;Set &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/t-sql/statements/set-lock-timeout-transact-sql&quot;&gt;lock_timeout&lt;/a&gt; to 1 second&lt;/li&gt;
&lt;li&gt;Try to truncate the table&lt;/li&gt;
&lt;li&gt;If we weren&#39;t successful, try again up to 5 times&lt;/li&gt;
&lt;li&gt;Mark all cache entries as expired if they are in the future&lt;/li&gt;
&lt;li&gt;Re-enable caching&lt;/li&gt;
&lt;li&gt;If we weren&#39;t able to truncate the table, throw an error&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;The time to wait depends on your environment - if your calling application has a timeout of 30 seconds, then in theory 31 seconds is long enough. In this case we&#39;ve deployed our wait as timeout x 1.5&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;alter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;proc&lt;/span&gt; DBCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbo_GetTopSellingProducts_CleanUp&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; nocount &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@CONTROL_NAME&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;dbo.GetTopSellingProducts&#39;&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@truncatedResultsTable&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;bit&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;update&lt;/span&gt; DBCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MasterControl&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; IsEnabled &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt;	CachedEntity &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@CONTROL_NAME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;waitfor&lt;/span&gt; delay &lt;span class=&quot;token string&quot;&gt;&#39;00:00:45&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; lock_timeout &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@retryCount&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@MAX_RETRY_COUNT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@retryCount&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@MAX_RETRY_COUNT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@truncatedResultsTable&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt; try&lt;br /&gt;			&lt;span class=&quot;token keyword&quot;&gt;truncate&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;table&lt;/span&gt; DBCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbo_GetTopSellingProductsResults&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@truncatedResultsTable&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt; try&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt; catch&lt;br /&gt;			&lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@retryCount&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt; catch&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;update&lt;/span&gt; DBCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbo_GetTopSellingProducts_Control&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; ExpiryDateTime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; getutcdate&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt;	ExpiryDateTime &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; getutcdate&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;update&lt;/span&gt; DBCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MasterControl&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; IsEnabled &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt;	CachedEntity &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@CONTROL_NAME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@truncatedResultsTable&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;throw &lt;span class=&quot;token number&quot;&gt;50001&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Failed to clean up results table for dbo.GetTopSellingProducts&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;br /&gt;go&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;trade-offs-and-design-decisions&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/03/pattern-sql-server-as-a-shared-cache-for-expensive-stored-procedures/#trade-offs-and-design-decisions&quot;&gt;Trade-offs and design decisions&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Adding caching in the database is fairly straightforward in theory - we store a copy of the results somewhere and re-use them if they are available (our early prototypes looked very similar to the approach outlined by &lt;a href=&quot;https://www.brentozar.com/archive/2013/12/how-to-cache-stored-procedure-results/&quot;&gt;this post from 2013&lt;/a&gt;). Some of the reason&#39;s that the caching solution deviated from the simple approach are documented below.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Some of these are specific to the environment the solution is being deployed in, and others are driven by the specific workload we were optimising for. Evaluate your own needs carefully before deploying some or all of this solution into production.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;no-lock&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/03/pattern-sql-server-as-a-shared-cache-for-expensive-stored-procedures/#no-lock&quot;&gt;No lock&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Not to be confused with &lt;code&gt;nolock&lt;/code&gt;! There exist multiple places in this solution where we could have attempted to coordinate access to resources (like results or the control table). We tried a few experiments, and none of them showed sufficient promise to convince us the additional complexity/risk (of long-held locks) was worth it. Our environment is highly transactional and so we opted to avoid any explicit locking, and prefer to duplicate work rather than serialise to perform work only once.&lt;/p&gt;
&lt;p&gt;Consider the example when the cache has expired and ten nodes call the procedure - all ten will do the work to populate the cache. This is a fairly extreme example, and in practice we never see this many duplicates (on occasion we do get some double-work).&lt;/p&gt;
&lt;p&gt;Being able to easily detect double work was a key driver for instrumentation.&lt;/p&gt;
&lt;h3 id=&quot;instrumentation&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/03/pattern-sql-server-as-a-shared-cache-for-expensive-stored-procedures/#instrumentation&quot;&gt;Instrumentation&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The caching solution features a &lt;code&gt;UseCount&lt;/code&gt; column, which allowed us to monitor the efficacy of the cache when deployed, and verify it was actually delivering value (alongside other measures from QueryStore and application monitoring). The impact of this logging is negligible, and provides us with additional data about how the applications are calling the procedure (as we also know what parameters are being used).&lt;/p&gt;
&lt;p&gt;Although a requirement of our implementation, the fact we log every time the cache is populated also allows us to trivially monitor when 2 or more nodes populate the cache at the same time. This is how we&#39;re able to confidently say the issue of double-work isn&#39;t impacting us.&lt;/p&gt;
&lt;h3 id=&quot;adding-to-the-same-database-vs-a-separate-database&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/03/pattern-sql-server-as-a-shared-cache-for-expensive-stored-procedures/#adding-to-the-same-database-vs-a-separate-database&quot;&gt;Adding to the same database vs. a separate database&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Our setup features availability groups, and we currently have no IO or networking issues. The benefit of placing the solution in the same database meant we had a much simpler deployment/failover/recovery story.&lt;/p&gt;
&lt;p&gt;The main concern deploying it into the primary database is the size of the cache - an early version was deployed to dev without a schedule for the clean-up job - the next morning we woke up to a hot mess of database space alerts.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This example is a very narrow result set with only 20 rows. In production we have deployed this solution on result sets in the 10,000+ rows range with 5+ columns. Being able to benchmark zero impact on our IO latencies was key to our decision to go ahead with a deployment in our primary AG database.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;cache-control-table&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/03/pattern-sql-server-as-a-shared-cache-for-expensive-stored-procedures/#cache-control-table&quot;&gt;Cache control table&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The simplest solution we tried was to store a single copy of the results for any given parameter. Our requirement for no locks as well as clear instrumentation quickly rendered this approach unsuitable.&lt;/p&gt;
&lt;p&gt;Adding a second table allowed us to adopt a pattern whereby we&#39;d populate the results before populating the control table. Early implementations populated the control table and then the results table, and under high load/error conditions it was possible for a procedure to think a cached result was ready (as it had checked the control table), only to encounter an empty results table.&lt;/p&gt;
&lt;p&gt;Another advantage with a second table was the ability to keep the indexing on the results table very simple, and only have to worry about the parameter-based index on the control table.&lt;/p&gt;
&lt;p&gt;Finally, supporting an empty result set isn&#39;t possible when only using a results table. Consider a product with no sales - the correct result set has no rows, and the only way to cache that is with a second table in addition to your results table.&lt;/p&gt;
&lt;h3 id=&quot;clean-up&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/03/pattern-sql-server-as-a-shared-cache-for-expensive-stored-procedures/#clean-up&quot;&gt;Clean-up&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Storing expired results meant that clean-up became a critical issue. Initial attempts to run deletes were fairly slow (not to mention expensive compared to a truncate), so we decided that we had to do the work required to support truncating the results table.&lt;/p&gt;
&lt;p&gt;The requirement to truncate was the motivation for the master control table - without some kind of coordination we couldn&#39;t find a way to safely truncate while the procedures were running. Stopping the procedures being called wasn&#39;t possible, but stopping the caching system was something we could support (the cache is a feature which aids performance - the system can run without it, albeit with reduced headroom).&lt;/p&gt;
&lt;p&gt;Setting the right frequency of the clean-up job depends heavily on how much data is generated and how much space you can dedicate to the cache solution.&lt;/p&gt;
&lt;h3 id=&quot;tempdb-usage-for-staging-results&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/03/pattern-sql-server-as-a-shared-cache-for-expensive-stored-procedures/#tempdb-usage-for-staging-results&quot;&gt;TempDb usage for staging results&lt;/a&gt;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;This is definitely a tradeoff you should only consider if you are very comfortable with the current state of your tempdb usage, and your ability to monitor it. I&#39;d suggest both tracking global tempdb metrics as well as query-level wait statistics, to ensure you&#39;re not unduly bottlenecking on tempdb&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The primary motivator for the results table is protection against errors - on encountering any kind of error inserting to the results table we&#39;re faced with the option of re-running the expensive logic (or even worse returning nothing!). By staging the data we always have something to return. This has saved us on at least one occasion when a data type change in a base table was not propagated to the cache results table.&lt;/p&gt;
&lt;p&gt;A second benefit of this approach is we only need to have one copy of the logic in the procedure. As we support running with DBCache enabled and disabled, an approach which doesn&#39;t feature a temp table typically featured a copy of the logic to populate the cache or return the results directly.&lt;/p&gt;
&lt;h2 id=&quot;implementing-caching&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/03/pattern-sql-server-as-a-shared-cache-for-expensive-stored-procedures/#implementing-caching&quot;&gt;Implementing caching&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A quick summary of what is needed to cache a procedure&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create the schema, sequence, and master control table&lt;/li&gt;
&lt;li&gt;Create the control and results table for your procedure&lt;/li&gt;
&lt;li&gt;Create the results clean-up procedure for your procedure
&lt;ul&gt;
&lt;li&gt;Schedule the clean-up job to run&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Modify the procedure to include caching
&lt;ul&gt;
&lt;li&gt;Until you insert a row in the master control table caching is disabled&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Insert a row in the master control table and turn caching on&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The changes to the base proc are fairly intricate, but after you&#39;ve done it a few times you&#39;ll have the pattern down.&lt;/p&gt;
&lt;p&gt;There are a few scenarios when you&#39;ll probably want to think twice about implementing this solution:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Huge number of parameters/very large parameters (varchar(max) anyone?)&lt;/li&gt;
&lt;li&gt;Proc is called from a small number of nodes (implement a simpler caching solution)&lt;/li&gt;
&lt;li&gt;IO or TempDB pressure&lt;/li&gt;
&lt;li&gt;Space pressure&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;future-ideas&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/03/pattern-sql-server-as-a-shared-cache-for-expensive-stored-procedures/#future-ideas&quot;&gt;Future Ideas&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Some additional ideas that have been considered/discussed. The further down this rabbit hole we go the more attractive that shared cache service starts to look!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adaptive cache - vary duration by parameter, current load&lt;/li&gt;
&lt;li&gt;Cache bypass - maybe some parameters are cheap enough to not need caching/only cache expensive parameters&lt;/li&gt;
&lt;li&gt;Smart expiry - if data hasn&#39;t changed enough don&#39;t expire the cache - leverage e.g. Timestamp/CDC&lt;/li&gt;
&lt;li&gt;Rotating partition scheme - truncating trailing partitions and caching into a fresh partition makes the clean-up job more complex but eliminates the need to pause (cache results now keys on PartitionId, ControlId)&lt;/li&gt;
&lt;/ul&gt;
</description>
      <pubDate>Wed, 21 Mar 2018 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2018/03/pattern-sql-server-as-a-shared-cache-for-expensive-stored-procedures/</guid>
    </item>
    <item>
      <title>Partitioned Clustered Columnstores - Mind your deltastores!</title>
      <link>https://tjaddison.com/blog/2018/03/partitioned-clustered-columnstores-mind-your-deltastores/</link>
      <description>&lt;p&gt;We use an hourly partitioning scheme coupled with a clustered columnstore to support ingestion and short-term retention of various telemetry data. A fairly common pattern is for us to deploy an hourly partition schema based on the &lt;code&gt;InsertDate&lt;/code&gt; of data (a column with a default of &lt;code&gt;getutcdate()&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;One thing we&#39;ve had to keep an eye on when using this kind of pattern is the number of open deltastores that can get left behind as inserts &#39;move on&#39; to a new partition.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/03/partitioned-clustered-columnstores-mind-your-deltastores/sRjSL4sBLd-295.avif 295w, https://tjaddison.com/blog/2018/03/partitioned-clustered-columnstores-mind-your-deltastores/sRjSL4sBLd-590.avif 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/03/partitioned-clustered-columnstores-mind-your-deltastores/sRjSL4sBLd-295.webp 295w, https://tjaddison.com/blog/2018/03/partitioned-clustered-columnstores-mind-your-deltastores/sRjSL4sBLd-590.webp 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2018/03/partitioned-clustered-columnstores-mind-your-deltastores/sRjSL4sBLd-295.jpeg 295w, https://tjaddison.com/blog/2018/03/partitioned-clustered-columnstores-mind-your-deltastores/sRjSL4sBLd-590.jpeg 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;sys.column_store_row_groups&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/03/partitioned-clustered-columnstores-mind-your-deltastores/sRjSL4sBLd-295.jpeg&quot; width=&quot;590&quot; height=&quot;217&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hourly partitions are used as they are also the unit of truncation as data ages out in our environment. Due to the pain of the &#39;dangling deltastores&#39; (as well as high partition counts impacting DMV usage) we&#39;re looking at making daily the smallest partition size we support.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When partitioning a clustered columnstore table you will have one or more deltastores &lt;em&gt;per partition&lt;/em&gt;. The largest I&#39;ve seen so far is 20 open in a single rowgroup (with row counts ranging from 1M to 10k), though 4 is about average for this particular environment.&lt;/p&gt;
&lt;p&gt;These open deltastores have a number of drawbacks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Increased size on disk for uncompressed rows&lt;/li&gt;
&lt;li&gt;Lack of rowgroup elimination for the rows in the deltastores&lt;/li&gt;
&lt;li&gt;Reduced query performance on queries which work in the rowstores&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The rest of this post will cover these in a bit more detail, as well as how to see if you&#39;re impacted and what to do about.&lt;/p&gt;
&lt;h2 id=&quot;increased-size-on-disk&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/03/partitioned-clustered-columnstores-mind-your-deltastores/#increased-size-on-disk&quot;&gt;Increased size on disk&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The first example is best shown by a table which had escaped any kind of index maintenance:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/03/partitioned-clustered-columnstores-mind-your-deltastores/SngzgWeG2E-295.avif 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/03/partitioned-clustered-columnstores-mind-your-deltastores/SngzgWeG2E-295.webp 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Impact of deltastores&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/03/partitioned-clustered-columnstores-mind-your-deltastores/SngzgWeG2E-295.jpeg&quot; width=&quot;295&quot; height=&quot;53&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;The deltastores might be less than 20% of the rows, but they&#39;re more than 80% of the space used for the table! In the example above compressing the open rowgroups reduced the table from 500GB to 150GB.&lt;/p&gt;
&lt;h2 id=&quot;lack-of-rowgroup-elimination&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/03/partitioned-clustered-columnstores-mind-your-deltastores/#lack-of-rowgroup-elimination&quot;&gt;Lack of rowgroup elimination&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://blogs.msdn.microsoft.com/sql_server_team/columnstore-index-performance-rowgroup-elimination/&quot;&gt;Rowgroup elimination&lt;/a&gt; is the ability of a query against the columnstore to skip entire rowgroups based on the metadata about min/max values stored for each column segment in the rowgroup (see &lt;code&gt;sys.column_store_segments&lt;/code&gt;). When querying our telemetry table for a single record in the last day (already leveraging partition elimination to limit our query), we&#39;ll also supply a predicate for a ServerId we know doesn&#39;t exist. This is beyond the max value that should exist in any rowgroup.&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;top&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Telemetry &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; t&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ServerId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;99999999&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;InsertDateTime&lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; cast&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;getutcdate&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; datetime2&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Partition elimination won&#39;t work if your data types are more precise than the column you&#39;re partitioning on. In our case using &lt;code&gt;getutcdate()&lt;/code&gt; would have been converted to datetime2(7), more precise than our &lt;code&gt;InsertDateTime&lt;/code&gt; column which is &lt;code&gt;datetime2(3)&lt;/code&gt;, resulting in a full table scan [ouch]. Segment elimination has no such qualms about data type precision!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;Table &#39;Telemetry&#39;. Scan count 101, logical reads 1548578, physical reads 0, read-ahead reads 1363208, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table &#39;Telemetry&#39;. Segment reads 0, segment skipped 107.

 SQL Server Execution Times:
   CPU time = 7672 ms,  elapsed time = 9444 ms.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The statistics of this query show that every segment was skipped (they were successfully eliminated), yet we spent 10 seconds doing an awful lot of reads. These reads were against the deltastores, as SQL Server had to check every single row to ensure none of them were for ServerId 99999999.&lt;/p&gt;
&lt;h2 id=&quot;reduced-query-performance&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/03/partitioned-clustered-columnstores-mind-your-deltastores/#reduced-query-performance&quot;&gt;Reduced query performance&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In addition to a lack of rowgroup elimination, there are additional performance issues when operating against deltastores. Although deltastore scans do seem to benefit from batch mode, they don&#39;t benefit from &lt;a href=&quot;https://blogs.msdn.microsoft.com/sql_server_team/columnstore-index-performance-sql-server-2016-aggregate-pushdown/&quot;&gt;aggregate pushdown&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Take the following query:&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@cutoff1&lt;/span&gt; datetime2&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;20180309&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@cutoff2&lt;/span&gt; datetime2&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;20180310&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Telemetry &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; t&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;InsertDateTime &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@cutoff1&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;InsertDateTime &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@cutoff2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The results when executed against our table with many open deltastores:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Table &#39;Telemetry&#39;. Scan count 114, logical reads 1589897, physical reads 235, read-ahead reads 1588889, lob logical reads 79781, lob physical reads 24, lob read-ahead reads 246172.
Table &#39;Telemetry&#39;. Segment reads 150, segment skipped 3.
Table &#39;Worktable&#39;. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server Execution Times:
CPU time = 18062 ms,  elapsed time = 12791 ms.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the results when executing against the same table where all deltastores have been compressed:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Table &#39;Telemetry&#39;. Scan count 2, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 97544, lob physical reads 61, lob read-ahead reads 292647.
Table &#39;Telemetry&#39;. Segment reads 259, segment skipped 6.
Table &#39;Worktable&#39;. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 641 ms,  elapsed time = 435 ms.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A tidy &lt;strong&gt;30x reduction in CPU&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id=&quot;how-to-check-if-you-re-impacted&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/03/partitioned-clustered-columnstores-mind-your-deltastores/#how-to-check-if-you-re-impacted&quot;&gt;How to check if you&#39;re impacted&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The dmv &lt;code&gt;sys.column_store_row_groups&lt;/code&gt; tells you the status of each of your row groups - you&#39;re looking for any open row groups that are no longer being inserted into, which is extremely common if you partition by an ever-increasing datetime value, more so as you have smaller partitions.&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; object_name&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;object_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; TableName&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;rg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt;  sys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;column_store_row_groups &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; rg&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; rg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state_desc &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;OPEN&#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also use &lt;a href=&quot;http://www.nikoport.com/&quot;&gt;Niko&#39;s&lt;/a&gt; &lt;a href=&quot;https://github.com/NikoNeugebauer/CISL&quot;&gt;Columnstore Indexes Script Library (CISL)&lt;/a&gt; to get a wealth of information about your columnstore indexes.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you have a lot of partitions some of these DMVs can be very slow - I&#39;d suggest dumping them into a #temp table for querying/analysis.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;fixing-the-problem&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/03/partitioned-clustered-columnstores-mind-your-deltastores/#fixing-the-problem&quot;&gt;Fixing the problem&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The good news is fixing this is easy:&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;alter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;index&lt;/span&gt; CCS_Telemetry &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Telemetry reorganize&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;compress_all_row_groups &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Although the command is easy, the impact to your system if you have a lot of open rowgroups might not be! You might instead opt to only compress a subset of partitions (much more typical if you have active partitions accepting inserts):&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;alter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;index&lt;/span&gt; CCS_Telemetry &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Telemetry reorganize&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;partition&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;compress_all_row_groups &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The final clause (&lt;code&gt;compress_all_row_groups&lt;/code&gt;) refers to the row groups in the partitions, not all row groups in the table.&lt;/p&gt;
&lt;p&gt;More information about index maintenance is available in the MSDN article &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/relational-databases/indexes/columnstore-indexes-defragmentation&quot;&gt;Columnstore indexes - defragmentation&lt;/a&gt;.&lt;/p&gt;
</description>
      <pubDate>Sat, 10 Mar 2018 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2018/03/partitioned-clustered-columnstores-mind-your-deltastores/</guid>
    </item>
    <item>
      <title>Keeping a work journal with VS Code</title>
      <link>https://tjaddison.com/blog/2018/02/keeping-a-work-journal-with-vs-code/</link>
      <description>&lt;p&gt;I&#39;ve recently started to keep a &lt;a href=&quot;https://peterlyons.com/leveling-up#your-work-journal&quot;&gt;work journal&lt;/a&gt;, and aside from being an incredibly useful document, it also gave me an excuse to learn a lot more about &lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;VS Code&lt;/a&gt;. After publishing an extension for VS Code yesterday, I&#39;m now declaring my work journal flow done (at least for now...), and the rest of this post will walk you through my setup.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/02/keeping-a-work-journal-with-vs-code/lZ0O1E2kqj-295.avif 295w, https://tjaddison.com/blog/2018/02/keeping-a-work-journal-with-vs-code/lZ0O1E2kqj-590.avif 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/02/keeping-a-work-journal-with-vs-code/lZ0O1E2kqj-295.webp 295w, https://tjaddison.com/blog/2018/02/keeping-a-work-journal-with-vs-code/lZ0O1E2kqj-590.webp 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2018/02/keeping-a-work-journal-with-vs-code/lZ0O1E2kqj-295.jpeg 295w, https://tjaddison.com/blog/2018/02/keeping-a-work-journal-with-vs-code/lZ0O1E2kqj-590.jpeg 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Sample Journal&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/02/keeping-a-work-journal-with-vs-code/lZ0O1E2kqj-295.jpeg&quot; width=&quot;590&quot; height=&quot;195&quot; /&gt;&lt;/picture&gt;
&lt;em&gt;A sample journal entry&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;workflow-goals&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/02/keeping-a-work-journal-with-vs-code/#workflow-goals&quot;&gt;Workflow goals&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In setting up a workflow there were a few requirements I had:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Easy to get up and running on a new machine&lt;/li&gt;
&lt;li&gt;Easy to run on multiple machines (home/work/laptops)&lt;/li&gt;
&lt;li&gt;Minimum possible friction to add something to the journal&lt;/li&gt;
&lt;li&gt;Works in VS Code (my go-to editor for anything that doesn&#39;t merit VS2017 or SSMS)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first two points boil down to sync, and crucially that sync should contain not only the journal itself but also the settings used to help achieve the third point (minimum friction edits) on every machine.&lt;/p&gt;
&lt;h2 id=&quot;keeping-things-in-sync&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/02/keeping-a-work-journal-with-vs-code/#keeping-things-in-sync&quot;&gt;Keeping things in sync&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The journal itself lives in a repository in &lt;a href=&quot;https://www.visualstudio.com/team-services/&quot;&gt;Visual Studio Team Services&lt;/a&gt;, in which it is free to host unlimited repos for up to 5 users. Having access to a repo either by SSH (which I use on most permanent machines) or via a Microsoft login (which allows for quick in-browser edits in a pinch) is helpful. I&#39;d have probably gone with GitHub if I had a plan which supported private repos.&lt;/p&gt;
&lt;p&gt;In order to keep the VS Code configuration in sync I use the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync&quot;&gt;Settings Sync&lt;/a&gt; extension, which uses a private GitHub gist (which is free!) to sync settings between instances of VS Code. With this configured all extensions/user settings/keybindings/etc., are synced between all instances of VS Code. It&#39;s &lt;em&gt;awesome&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;configuring-the-editor&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/02/keeping-a-work-journal-with-vs-code/#configuring-the-editor&quot;&gt;Configuring the editor&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Aesthetics aside (Solarized Light and the &lt;a href=&quot;https://github.com/tonsky/FiraCode&quot;&gt;Fira Code&lt;/a&gt; font if you&#39;re curious), the most important actions I take on the journal are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pull from the repo&lt;/li&gt;
&lt;li&gt;Add a timestamp&lt;/li&gt;
&lt;li&gt;Add, Commit, and Push changes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The pull/push typically bookend my day with each machine I work at. Pulling from the repo is easy (builtin command), but adding a timestamp and the add/commit/push combination don&#39;t have an in-built solution.&lt;/p&gt;
&lt;p&gt;For the timestamp I use the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=jsynowiec.vscode-insertdatestring&quot;&gt;Insert Date String&lt;/a&gt; extension configured with the following custom format:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token property&quot;&gt;&quot;insertDateString.format&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;DDD MMM DD hh:mm:ss YYYY&#92;n&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Binding this to a key means hitting the key inserts a timestamp and puts the cursor on a newline.&lt;/p&gt;
&lt;p&gt;Solving for add/commit/push was actually much harder than I thought it would be, and after looking at a few extensions which came close (like &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=arafathusayn.git-urgent&quot;&gt;Git Urgent!&lt;/a&gt; and &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=ivangabriele.vscode-git-add-and-commit&quot;&gt;Git Add &amp;amp; Commit&lt;/a&gt;) I ended up writing my own extension - &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=taddison.gitlazy&quot;&gt;GitLazy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Although it was a fairly simple addon, it was my first extension and my first look at TypeScript, so I&#39;ve learned a whole lot from that. In terms of time saved vs. time spent I think it should pay off in...a few years! My default commit message is configured as:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token property&quot;&gt;&quot;gitlazy.commitMessage&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Update journal&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;putting-it-all-together&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/02/keeping-a-work-journal-with-vs-code/#putting-it-all-together&quot;&gt;Putting it all together&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One of the first things I do after logging in to a machine is now go to the second desktop (I&#39;m using Windows 10), open my journal VS Code workspace, and put it into Zen mode (CTRL-K, Z by default). From that point on I&#39;ll flip back to that desktop (WinKey+Ctrl+Right) whenever I want to log something.&lt;/p&gt;
&lt;p&gt;I use markdown by default, and most of the time log code in appropriately delineated blocks (as per the above screenshot), though I don&#39;t mind simply pasting something in if I want to keep it for reference.&lt;/p&gt;
&lt;h2 id=&quot;coming-soon&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/02/keeping-a-work-journal-with-vs-code/#coming-soon&quot;&gt;Coming soon&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Given the amount of time I spent optimising the flow of add/commit/push I find it highly unlikely I&#39;ll be able to leave the flow alone for too long. I&#39;ve considered making the code workspace a startup task, or even pinning the workspace to the start menu for even faster switching (e.g. WinKey+1).&lt;/p&gt;
&lt;p&gt;I&#39;m debating right now whether or not to put some outputs from meetings in the journal or not - for 1:1s I typically keep notes organised per-person (in addition to a shared agenda) on something like OneNote or a word document in SharePoint. I&#39;m now wondering if they should also get dumped into the log, or if perhaps migrating them to source-controlled markdown is a better format.&lt;/p&gt;
&lt;p&gt;I&#39;m also looking forward to writing something to parse out the journal, not to gain anything in particular from it right now, more because it&#39;ll be a chance to write some parsing code and see what can be done with the data...&lt;/p&gt;
</description>
      <pubDate>Wed, 28 Feb 2018 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2018/02/keeping-a-work-journal-with-vs-code/</guid>
    </item>
    <item>
      <title>Service Broker, Temporal Tables, and the &#39;Data modification failed&#39; error</title>
      <link>https://tjaddison.com/blog/2018/02/service-broker-temporal-tables-and-the-data-modification-failed-error/</link>
      <description>&lt;p&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/sql/relational-databases/tables/temporal-tables&quot;&gt;Temporal tables&lt;/a&gt; are a fantastic feature which we&#39;ve enjoyed rolling out to replace some hand-rolled logging. Adding system versioning to a table has been mostly straightforward, though last week one of my colleagues saw some really odd behaviour that took the team a while to debug. Now we&#39;ve understood the problem we&#39;re able to reproduce it 100% of the time (and subsequently come up with a workaround), though it definitely had us scratching our heads for a while - thanks for a super-interesting problem Ola!&lt;/p&gt;
&lt;p&gt;We&#39;ve previously had experience with highly concurrent modifications to a single row causing a data-modification error, and in every case we&#39;d end up tracking down a bug which was causing unnecessary concurrent modifications.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Msg 13535, Level 16, State 0, Procedure HandleProcessPayment, Line 20 [Batch Start Line 0]
Data modification failed on system-versioned table &#39;TemporalBroker.dbo.Payment&#39; because transaction time was earlier than period start time for affected records.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What had us really confused this time was that this was a very low-volume process, and we didn&#39;t observe any concurrency around the insert/update activity for the single row (confirmed with exhaustive XEvent-ing!).&lt;/p&gt;
&lt;h2 id=&quot;the-setup&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/02/service-broker-temporal-tables-and-the-data-modification-failed-error/#the-setup&quot;&gt;The Setup&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One of our applications uses service broker fairly heavily, the simplified flow looks something like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Web app inserts a payment record in state &#39;ready to pay&#39;&lt;/li&gt;
&lt;li&gt;Web app queues a message on service broker with the instruction to pay&lt;/li&gt;
&lt;li&gt;Payments app is listening on that queue for instructions&lt;/li&gt;
&lt;li&gt;Payments app receives the message in a transaction&lt;/li&gt;
&lt;li&gt;Payments app requests a payment&lt;/li&gt;
&lt;li&gt;Payments app updates the status of the payment to &#39;requested&#39;&lt;/li&gt;
&lt;li&gt;Payments app commits the transaction&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After implementing system versioning on the table we started to see errors on the step which updated the payment.&lt;/p&gt;
&lt;p&gt;The transactional flow (shown by time T, and session S - in this case session 1 is the payments app, session 2 is the web app) looks something like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;T1, S1 - Begin Tran&lt;/li&gt;
&lt;li&gt;T2, S1 - &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/t-sql/statements/receive-transact-sql&quot;&gt;Waitfor receive&lt;/a&gt;...&lt;/li&gt;
&lt;li&gt;T3, S2 - Insert Payment&lt;/li&gt;
&lt;li&gt;T4, S2 - Queue PaymentProcess message&lt;/li&gt;
&lt;li&gt;T4, S1 - Receive PaymentProcess message&lt;/li&gt;
&lt;li&gt;T5, S1 - Update Payment - error&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The clue was sitting in the error message - &lt;code&gt;transaction time was earlier than period start time for affected records&lt;/code&gt;. At time T5 when the update happens the row is stamped with the time from T1, not T5. Because we use a transaction to pop messages and wait for one to arrive, we can end up processing the message at a timestamp before the Payment record is inserted. This behaviour is by design - and is called out pretty clearly in the &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/relational-databases/tables/temporal-tables#how-does-temporal-work&quot;&gt;official docs&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The times recorded in the system datetime2 columns are based on the begin time of the transaction itself. For example, all rows inserted within a single transaction will have the same UTC time recorded in the column corresponding to the start of the SYSTEM_TIME period.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As well as in the &lt;a href=&quot;http://standards.iso.org/ittf/PubliclyAvailableStandards/c060394_ISO_IEC_TR_19075-2_2015.zip&quot;&gt;ISO technical report on SQL Support for Time-Related Information&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;An UPDATE statement on a system-versioned table first inserts a copy of the old row with its system-time period end time set to the transaction timestamp, indicating that the row ceased to be current as of the transaction timestamp. It then updates the row while changing its system-period start time to the transaction timestamp, indicating that the updated row to be the current system row as of the transaction timestamp.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In all of our previous troubleshooting we were dealing with very short (typically implicit) transactions, and so we were incorrectly thinking about concurrency at the statement level, rather than the transaction level.&lt;/p&gt;
&lt;h2 id=&quot;repro&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/02/service-broker-temporal-tables-and-the-data-modification-failed-error/#repro&quot;&gt;Repro&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;If you&#39;d like to try this repro out yourself you can download &lt;a href=&quot;https://tjaddison.com/blog/2018/02/service-broker-temporal-tables-and-the-data-modification-failed-error/createobjects.sql&quot;&gt;this script&lt;/a&gt; to create the database and all objects required.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The two procedures we&#39;ll be looking at are the one which inserts the payment request:&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;alter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;procedure&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;RequestPayment&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;insert&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;into&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Payment&lt;br /&gt;	&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; PaymentStateId &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;values&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@paymentId&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; scope_identity&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@message&lt;/span&gt; xml &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;br /&gt;            N&lt;span class=&quot;token string&quot;&gt;&#39;&amp;lt;ProcessPayment&gt;&amp;lt;PaymentId&gt;&#39;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; cast&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@paymentId&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; nvarchar&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&amp;lt;/PaymentId&gt;&amp;lt;/ProcessPayment&gt;&#39;&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@dialogId&lt;/span&gt; uniqueidentifier&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt; dialog &lt;span class=&quot;token variable&quot;&gt;@dialogId&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; service PaymentProcessService&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;to&lt;/span&gt; service &lt;span class=&quot;token string&quot;&gt;&#39;PaymentProcessService&#39;&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; contract PaymentContract&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt; encryption &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;off&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;	send &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; conversation &lt;span class=&quot;token variable&quot;&gt;@dialogId&lt;/span&gt;&lt;br /&gt;	message &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; ProcessPayment&lt;br /&gt;	&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@message&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the procedure which processes the payment:&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;alter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;procedure&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;HandleProcessPayment&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; nocount &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@message&lt;/span&gt; xml&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;tran&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;waitfor&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;			receive &lt;span class=&quot;token keyword&quot;&gt;top&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@message&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; message_body&lt;br /&gt;			&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PaymentQueue&lt;br /&gt;		&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; timeout &lt;span class=&quot;token number&quot;&gt;10000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@message&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;null&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;token keyword&quot;&gt;commit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;			&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@paymentId&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@message.value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;(/ProcessPayment/PaymentId)[1]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;int&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;update&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Payment&lt;br /&gt;			&lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; PaymentStateId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; PaymentId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@paymentId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;commit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this trivialised example the procedure does all the work, in the actual environment there was a single transaction and multiple commands executed (as well as the third-party calls to actually initiate a payment!).&lt;/p&gt;
&lt;p&gt;To reproduce the error, execute &lt;code&gt;dbo.HandleProcessPayment&lt;/code&gt; in one session (which will wait up to 10 seconds for a message to arrive), and then run &lt;code&gt;dbo.RequestPayment&lt;/code&gt; in another session - you&#39;ll see the HandleProcessPayment procedure error out.&lt;/p&gt;
&lt;h2 id=&quot;workarounds&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2018/02/service-broker-temporal-tables-and-the-data-modification-failed-error/#workarounds&quot;&gt;Workarounds&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Depending on what you can change there are a few ways to work around this that can work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Remove the timeout from the waitfor (if there is no sleep in the application you&#39;ll end up calling this a lot)&lt;/li&gt;
&lt;li&gt;Remove the timeout from the waitfor and add a sleep (this increases average time to respond to a message)&lt;/li&gt;
&lt;li&gt;Add a wait between inserting the Payment and sending the message (this also increases average time to respond)&lt;/li&gt;
&lt;li&gt;Retry on receiving the error (in our case we hit this edge case 100% of the time on the first attempt to process)&lt;/li&gt;
&lt;li&gt;Don&#39;t use system versioning (a trigger based solution with getutcdate() doesn&#39;t have this problem)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of these are great options (in the end we lowered the waitfor timeout to a few hundred milliseconds and added the same delay before queueing the message - this eliminated all the errors), and we&#39;ll be considering what changes we could make to our messaging in the future to remove the requirement for a workaround. The current favoured option is a switch away from service broker where we don&#39;t need all the complexity and polling from the app.&lt;/p&gt;
</description>
      <pubDate>Sat, 17 Feb 2018 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2018/02/service-broker-temporal-tables-and-the-data-modification-failed-error/</guid>
    </item>
    <item>
      <title>Changing the owner of an SSRS subscription with PowerShell</title>
      <link>https://tjaddison.com/blog/2018/01/changing-the-owner-of-an-ssrs-subscription-with-powershell.1/</link>
      <description>&lt;p&gt;If you&#39;re responsible for an SSRS instance you&#39;ve probably got a script somewhere to handle changing the owner of a subscription (typically done when a user leaves/changes role and they&#39;ve got subscriptions that need to keep running).&lt;/p&gt;
&lt;p&gt;The official docs do provide a &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/reporting-services/subscriptions/manage-subscription-owners-and-run-subscription-powershell#bkmk_change_all_1_subscription&quot;&gt;PowerShell script&lt;/a&gt; you can use to do this, though it is a little inefficient if you&#39;ve got any latency between you and your instance and/or you have a lot of reports (it gets all reports, and then for each one gets all subscriptions).&lt;/p&gt;
&lt;p&gt;SSRS exposes an API which lets you get all subscriptions in one go, which is much faster. The script below will take all subscriptions owned by Tim and assign them to Tom.&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$oldOwner&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;foocorp&#92;tim.addison&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$newOwner&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;foocorp&#92;tom.addison&quot;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$rs2010&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;New-WebServiceProxy&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Uri &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost/ReportServer/ReportService2010.asmx&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Namespace SSRS&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ReportingService2010 &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;UseDefaultCredential&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$subsToMove&lt;/span&gt; = &lt;span class=&quot;token variable&quot;&gt;$rs2010&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ListSubscriptions&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Where-Object&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$_&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Owner &lt;span class=&quot;token operator&quot;&gt;-like&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$oldOwner&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$subsToMove&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ForEach-Object&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$rs2010&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ChangeSubscriptionOwner&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$_&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SubscriptionID&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$newOwner&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$subsToMove&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Select-Object&lt;/span&gt; Path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Report &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Format-Table&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;You&#39;ll need to update the Uri in the above example if your report server is anywhere other than localhost.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The script will also output the list of subscriptions that have been modified.&lt;/p&gt;
&lt;p&gt;Since late 2016 there has been a &lt;a href=&quot;https://www.powershellgallery.com/packages/ReportingServicesTools&quot;&gt;PowerShell module&lt;/a&gt; available that has started to provide a comprehensive set of SSRS administration functions, though at the time of writing it has the same items-then-subscriptions pattern which makes it much slower than the above script.&lt;/p&gt;
&lt;p&gt;Finally, it&#39;s worth noting that if you only manage a small number of instances (or perhaps you&#39;re just more comfortable in T-SQL) there is a simple script you can use (originally posted at https://blogs.msdn.microsoft.com/miah/2008/07/10/tip-change-the-owner-of-sql-reporting-services-subscription/, which now 404s and I can&#39;t find the new home).&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;DECLARE&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@OldUserID&lt;/span&gt; uniqueidentifier&lt;br /&gt; &lt;span class=&quot;token keyword&quot;&gt;DECLARE&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@NewUserID&lt;/span&gt; uniqueidentifier&lt;br /&gt; &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@OldUserID&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;UserID &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Users &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; UserID &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;foocorp&#92;tim.addison&#39;&lt;/span&gt;&lt;br /&gt; &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;  &lt;span class=&quot;token variable&quot;&gt;@NewUserID&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;UserID &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Users &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; UserName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;foocorp&#92;tom.addison&#39;&lt;/span&gt;&lt;br /&gt; &lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Subscriptions &lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; OwnerID &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@NewUserID&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; OwnerID &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@OldUserID&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;Big thanks to Jim for nudging me in the PowerShell direction on this one - what a fantastic little cmdlet New-WebServiceProxy is!&lt;/em&gt;&lt;/p&gt;
</description>
      <pubDate>Wed, 24 Jan 2018 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2018/01/changing-the-owner-of-an-ssrs-subscription-with-powershell.1/</guid>
    </item>
    <item>
      <title>SQLChecks v1 Released</title>
      <link>https://tjaddison.com/blog/2018/01/sqlchecks-v1-released/</link>
      <description>&lt;p&gt;After using SQLChecks to help tame our production instances for a few months, &lt;a href=&quot;https://github.com/taddison/SQLChecks/releases/tag/1.0&quot;&gt;v1 has now been released&lt;/a&gt;. This first release includes documentation on all supported tests, as well as limited guidelines on how to structure any new tests/PowerShell functions.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2018/01/sqlchecks-v1-released/wolf0bJPED-295.avif 295w, https://tjaddison.com/blog/2018/01/sqlchecks-v1-released/wolf0bJPED-590.avif 590w, https://tjaddison.com/blog/2018/01/sqlchecks-v1-released/wolf0bJPED-885.avif 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2018/01/sqlchecks-v1-released/wolf0bJPED-295.webp 295w, https://tjaddison.com/blog/2018/01/sqlchecks-v1-released/wolf0bJPED-590.webp 590w, https://tjaddison.com/blog/2018/01/sqlchecks-v1-released/wolf0bJPED-885.webp 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2018/01/sqlchecks-v1-released/wolf0bJPED-295.jpeg 295w, https://tjaddison.com/blog/2018/01/sqlchecks-v1-released/wolf0bJPED-590.jpeg 590w, https://tjaddison.com/blog/2018/01/sqlchecks-v1-released/wolf0bJPED-885.jpeg 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Pester Tests&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2018/01/sqlchecks-v1-released/wolf0bJPED-295.jpeg&quot; width=&quot;885&quot; height=&quot;381&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Get the &lt;a href=&quot;https://github.com/taddison/SQLChecks&quot;&gt;latest version of SQLChecks from GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The documentation in some cases includes suggestions for config values, and explanations on why you should care about testing for that particular value. These values and recommendations are drawn from a combination official sources (e.g. &lt;a href=&quot;https://blogs.technet.microsoft.com/mspfe/2013/01/08/10-top-sql-server-issues-uncovered-by-the-sql-server-risk-assessment-program/&quot;&gt;SQLRAP&lt;/a&gt;), books &amp;amp; blogs, as well as hard-won experience.&lt;/p&gt;
&lt;p&gt;The goal with these tests is not just to identify areas where you&#39;re not meeting policy today, but to ensure that over time you don&#39;t have any accidental regressions (e.g. new databases, people changing configuration settings for testing and not putting them back...)&lt;/p&gt;
&lt;p&gt;As an example consider the transaction log growth tests:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Max transaction log fixed growth&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Reports on any database which has a fixed growth larger than the config value.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;Transaction log with percentage growth&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Reports on any database log with a percentage growth configured.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Even though you&#39;ll endeavour to design your environment to never need an auto-growth (by e.g. right sizing your databases, managing log backups, monitoring replica redo, managing long-running transactions), if your database log file does ever fill and triggers that growth you&#39;ll want it to complete quickly (as all log activity is suspended while the log grows). Because the log is zeroed out before use you could find yourself waiting a long time when your 1TB database log attempts to grow by 10%.&lt;/p&gt;
&lt;p&gt;Having this check in place alongside existing measures you might have to manage log file growths (setting model appropriately for new databases, one-time reviews of all databases in violation, etc.) means that when something invariably slips through the cracks (or a DBA fat-fingers an update) you&#39;ll know about it and be able to take corrective action.&lt;/p&gt;
</description>
      <pubDate>Sun, 07 Jan 2018 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2018/01/sqlchecks-v1-released/</guid>
    </item>
    <item>
      <title>Visualising stored procedure call trees with SQLSpelunker</title>
      <link>https://tjaddison.com/blog/2017/12/visualising-stored-procedure-call-trees-with-sqlspelunker/</link>
      <description>&lt;p&gt;Whether debugging a problem in an existing system, or planning changes to an existing (complex) system, knowing how the call graph for a bit of SQL looks has been invaluable in quickly understanding the domain.&lt;/p&gt;
&lt;p&gt;If you&#39;ve worked in any system where a lot of the logic ends up in the database, you&#39;ll probably be nodding your head and remembering &#39;that&#39; procedure which looked pretty simple to start with, and after following a few dependencies you were suddenly deep down the rabbit hole... (the longest I&#39;ve looked at so far branches out into ~100 procedures).&lt;/p&gt;
&lt;p&gt;Taking inspiration from &lt;a href=&quot;http://port1433.com/2017/12/04/whats-in-the-box-validating-sql-server-scripts-with-powershell/&quot;&gt;What&#39;s in the box? Validating SQL Scripts with Powershell&lt;/a&gt; and &lt;a href=&quot;https://the.agilesql.club/blog/Ed-Elliott/2015-11-07/Get-Started-With-The-ScriptDom&quot;&gt;Get Started with the ScriptDom&lt;/a&gt; I built &lt;a href=&quot;https://www.github.com/taddison/SQLSpelunker&quot;&gt;SQLSpelunker&lt;/a&gt; to allow you to quickly go from a stored procedure name to a visual of the call tree for that procedure:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;c:&#92;SQLSpelunker&amp;gt;dotnet run &amp;quot;server=localhost;initial catalog=tempdb;integrated security=SSPI&amp;quot; &amp;quot;exec dbo.ProcOne;&amp;quot;

tempdb.dbo.ProcOne
-tempdb.dbo.ProcTwo
--tempdb.dbo.ProcThree
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can get started right away by grabbing the &lt;a href=&quot;https://github.com/taddison/SQLSpelunker&quot;&gt;source from Github&lt;/a&gt; or download the latest build from the &lt;a href=&quot;https://github.com/taddison/SQLSpelunker/releases&quot;&gt;releases page&lt;/a&gt;. To build or run the code you&#39;ll need the &lt;a href=&quot;https://www.microsoft.com/net/download&quot;&gt;.Net Core 2.0 SDK&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Read on for more details about what is currently supported, as well as details of how it works under the hood.&lt;/p&gt;
&lt;h2 id=&quot;features&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/12/visualising-stored-procedure-call-trees-with-sqlspelunker/#features&quot;&gt;Features&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;visualising-simple-call-trees&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/12/visualising-stored-procedure-call-trees-with-sqlspelunker/#visualising-simple-call-trees&quot;&gt;Visualising simple call trees&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Given the following schema:&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;use&lt;/span&gt; tempdb&lt;br /&gt;go&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;procedure&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ProcOne&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;exec&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ProcTwo&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;go&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;procedure&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ProcTwo&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;exec&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ProcThree&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;go&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;procedure&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ProcThree&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; @&lt;span class=&quot;token variable&quot;&gt;@servername&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; ServerName&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;go&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running SQLSpelunker against that database and the script &lt;code&gt;exec dbo.ProcOne&lt;/code&gt; will generate the following output:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tempdb.dbo.ProcOne
-tempdb.dbo.ProcTwo
--tempdb.dbo.ProcThree
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ProcOne calls ProcTwo calls ProcThree. Fairly straightforward.&lt;/p&gt;
&lt;h3 id=&quot;infinite-loop-detection&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/12/visualising-stored-procedure-call-trees-with-sqlspelunker/#infinite-loop-detection&quot;&gt;Infinite loop detection&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;While walking the procedure tree if a procedure is encountered that exists anywhere as a parent, that branch stops at the repeat call. This will catch both simple cases (ProcA calls ProcA) as well as multi-step infinite loops (ProcA calls ProcB calls ProcA).&lt;/p&gt;
&lt;p&gt;If we now add one more procedure to our schema:&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;procedure&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CallsItself&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;exec&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CallsItself&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;exec&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ProcOne&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;go&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running SQLSpelunker against the same database with the script &lt;code&gt;exec dbo.NeverCallsItself&lt;/code&gt; will return the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tempdb.dbo.CallsItself
-tempdb.dbo.CallsItself [*]
-tempdb.dbo.ProcOne
--tempdb.dbo.ProcTwo
---tempdb.dbo.ProcThree
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The asterisk (&lt;code&gt;[*]&lt;/code&gt;) signifies the procedure has previously been called by a parent procedure, and SQLSpelunker will stop trying to walk the call tree.&lt;/p&gt;
&lt;h3 id=&quot;default-schema&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/12/visualising-stored-procedure-call-trees-with-sqlspelunker/#default-schema&quot;&gt;Default schema&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If a procedure is executed without a schema name then it will be assumed that the default schema of the calling user is dbo. So the following statement:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;exec proc;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When parsed result in:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tempdb.dbo.proc
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that the database name is defaulted based on the connection string, or in the case of cross-database calls the &#39;current&#39; database the procedure is executing in. If you do not specify a database name on the connection string SQLSpelunker will throw an error.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;cross-database-calls&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/12/visualising-stored-procedure-call-trees-with-sqlspelunker/#cross-database-calls&quot;&gt;Cross-database calls&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The procedure calls can span multiple databases. Given the following new procedures:&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;use&lt;/span&gt; tempdb&lt;br /&gt;go&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;procedure&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CrossDBCall&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;exec&lt;/span&gt; master&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ILiveInMaster&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;go&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;use&lt;/span&gt; master&lt;br /&gt;go&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;procedure&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ILiveInMaster&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;exec&lt;/span&gt; tempdb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CrossDBCall&lt;br /&gt;go&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&#39;d see the following call chain:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tempdb.dbo.CrossDBCall
-master.dbo.ILiveInMaster
--tempdb.dbo.CrossDBCall [*]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;limitations&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/12/visualising-stored-procedure-call-trees-with-sqlspelunker/#limitations&quot;&gt;Limitations&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;exec-procedure&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/12/visualising-stored-procedure-call-trees-with-sqlspelunker/#exec-procedure&quot;&gt;Exec @procedure&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Only stored procedures executed by identifier are supported. If your statement contains any SQL in the following form it won&#39;t be reported:&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@procName&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;dbo.WontBeSeen&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;exec&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@procName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;every-procedure-is-output&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/12/visualising-stored-procedure-call-trees-with-sqlspelunker/#every-procedure-is-output&quot;&gt;Every procedure is output&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The current implementation will report &lt;em&gt;every procedure&lt;/em&gt; called in the code, even if that procedure isn&#39;t reachable (e.g. hidden behind an always-false statement).&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;procedure&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ThisMaybeDoesNothing&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;/* Never reachable */&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;exec&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ProcOne&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; rand&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.1&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;/* Sometimes reachable */&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;exec&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ProcTwo&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;br /&gt;go&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This produces:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tempdb.dbo.ThisMaybeDoesNothing
-dbo.ProcOne
-dbo.ProcTwo
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;missing-procedures-are-still-reported&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/12/visualising-stored-procedure-call-trees-with-sqlspelunker/#missing-procedures-are-still-reported&quot;&gt;Missing procedures are still reported&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When looking up the definition of a procedure if the definition can&#39;t be retrieved the call will still be reported. This is probably a feature more than a limitation as some procs which do exist won&#39;t have their definition reported (a good example being msdb.dbo.sp_send_dbmail). In the future &#39;missing&#39; procs may get an annotation.&lt;/p&gt;
&lt;h2 id=&quot;usage&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/12/visualising-stored-procedure-call-trees-with-sqlspelunker/#usage&quot;&gt;Usage&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The command line arguments for the SQLSpelunker console app are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A connection string to the initial database (which must contain a database)&lt;/li&gt;
&lt;li&gt;The SQL fragment that should be parsed and walked&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An example connection string that connects to tempdb on the localhost instance with windows authentication is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server=localhost;initial catalog=tempdb;integrated security=SSPI
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;how-it-works&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/12/visualising-stored-procedure-call-trees-with-sqlspelunker/#how-it-works&quot;&gt;How it works&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The flow of the program looks something like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Parse the SQL fragment and extract all stored procedure calls&lt;/li&gt;
&lt;li&gt;For each stored procedure call get the definition if it isn&#39;t already in cache&lt;/li&gt;
&lt;li&gt;For each stored procedure parse the definition and extract all stored procedure calls&lt;/li&gt;
&lt;li&gt;Repeat until there are no more unique calls (e.g. also break on infinite loops)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;SQL fragments are parsed using TransactSql ScriptDom - the &lt;a href=&quot;https://msdn.microsoft.com/en-us/library/microsoft.sqlserver.transactsql.scriptdom.aspx?f=255&amp;amp;MSPPError=-2147217396&quot;&gt;official docs&lt;/a&gt; are a bit bare-bones, so I&#39;d suggest starting with &lt;a href=&quot;https://the.agilesql.club/tags/scriptdom/&quot;&gt;Ed Elliot&#39;s excellent blog category&lt;/a&gt; if you want to learn more.&lt;/p&gt;
&lt;p&gt;Stored procedure definitions are extracted using the &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/t-sql/functions/object-definition-transact-sql&quot;&gt;object_definition&lt;/a&gt; function.&lt;/p&gt;
&lt;h2 id=&quot;the-future-of-sqlspelunker&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/12/visualising-stored-procedure-call-trees-with-sqlspelunker/#the-future-of-sqlspelunker&quot;&gt;The future of SQLSpelunker&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I was pleasantly surprised at how little code was needed to solve the core problem of getting the definition and parsing all procedure calls.&lt;/p&gt;
&lt;p&gt;The console app is helpful but not particularly easy to use (a quick-start with colleagues often took the form of &#39;download .net core, clone the repo, and then run this command - no there isn&#39;t just an exe you can download...&#39;). A web version or powershell cmdlet are ideas I&#39;m playing with to make it more accessible.&lt;/p&gt;
&lt;p&gt;The other feature which would be great is &#39;what calls this procedure?&#39;. Obviously that is a very different challenge to walking a tree (you&#39;re forced to go an get every procedure - potentially from every database! When you&#39;ve got thousands of procedures that starts to take time, and keeping it snappy/interactive was a goal, and one of the reasons I didn&#39;t just recommend some of the existing tools out there for exploring schema).&lt;/p&gt;
&lt;p&gt;Moving away from procedures only I&#39;d love to extend this idea to report what tables/columns are being modified, though the way that gets visualised/interacted with is what I&#39;m struggling with right now (is the question &#39;show me everything&#39; or &#39;show me all procs in this chain which touch table foo/column bar?&#39;).&lt;/p&gt;
&lt;h2 id=&quot;the-longest-deepest-proc-i-ve-seen-so-far&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/12/visualising-stored-procedure-call-trees-with-sqlspelunker/#the-longest-deepest-proc-i-ve-seen-so-far&quot;&gt;The longest/deepest proc I&#39;ve seen so far&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Each line below represents a nested call to another proc all branching out from a single call. Curious to know what else is out there :)&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/12/visualising-stored-procedure-call-trees-with-sqlspelunker/8QWE4_nuc8-111.avif 111w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/12/visualising-stored-procedure-call-trees-with-sqlspelunker/8QWE4_nuc8-111.webp 111w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Namespace configuration&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/12/visualising-stored-procedure-call-trees-with-sqlspelunker/8QWE4_nuc8-111.jpeg&quot; width=&quot;111&quot; height=&quot;484&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
</description>
      <pubDate>Sat, 23 Dec 2017 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2017/12/visualising-stored-procedure-call-trees-with-sqlspelunker/</guid>
    </item>
    <item>
      <title>Auto deflating Event Hubs with a function app</title>
      <link>https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/</link>
      <description>&lt;p&gt;EventHubs have supported &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/event-hubs/event-hubs-auto-inflate&quot;&gt;auto-inflate/scale-up&lt;/a&gt; for a while now, but don&#39;t come with an equivalent to auto deflate/scale-down. If your workload doesn&#39;t have sustained throughput requirements you&#39;ll probably benefit from periodically scaling back.&lt;/p&gt;
&lt;p&gt;Assuming you allow your hub to inflate to 10 throughput units but most of the time you only need 1, at current pricing that represents an overpayment of $0.27/hour, or $2,300/year. Over multiple hubs (don&#39;t forget your dev/test instances!) it quickly adds up.&lt;/p&gt;
&lt;p&gt;Doing this manually is possible (and right now the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/event-hubs/event-hubs-auto-inflate&quot;&gt;comments on the auto-inflate article&lt;/a&gt; suggest this is the way to go), though we can build and deploy a simple function app to take care of all of our event hubs periodically. The great thing about auto-inflate is that if we do scale back and the workload needs more throughput, it&#39;ll scale right back up again.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/wy_9POdgcX-295.avif 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/wy_9POdgcX-295.webp 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Namespace configuration&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/wy_9POdgcX-295.jpeg&quot; width=&quot;295&quot; height=&quot;268&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Where is the auto deflate checkbox?&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;pre-requisites&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/#pre-requisites&quot;&gt;Pre-requisites&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In order to deploy the solution we&#39;ll need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Visual Studio 2017 with Azure Development workload (Community edition is fine)&lt;/li&gt;
&lt;li&gt;A app service to deploy a function app to&lt;/li&gt;
&lt;li&gt;A service principal which has owner permissions on the resource group(s) containing the namespaces we want to scale down&lt;/li&gt;
&lt;li&gt;Details of your tenant, subscription, and resource group(s)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;function-app&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/#function-app&quot;&gt;Function App&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-azure-function&quot;&gt;Azure documentation&lt;/a&gt; will take you through creating a function app if you don&#39;t already have one (which will create the required app service).&lt;/p&gt;
&lt;p&gt;Once the function app is created grab the values for &lt;strong&gt;AzureWebJobsStorage&lt;/strong&gt; and &lt;strong&gt;AzureWebJobsDashboard&lt;/strong&gt; from the application settings (they&#39;re both the same value). These are needed for you to test your function locally.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/qPDcENqKG2-295.avif 295w, https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/qPDcENqKG2-590.avif 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/qPDcENqKG2-295.webp 295w, https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/qPDcENqKG2-590.webp 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/qPDcENqKG2-295.jpeg 295w, https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/qPDcENqKG2-590.jpeg 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Configuration details&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/qPDcENqKG2-295.jpeg&quot; width=&quot;590&quot; height=&quot;94&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h3 id=&quot;service-principal&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/#service-principal&quot;&gt;Service Principal&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To create the service principal we&#39;re going to use &lt;a href=&quot;https://docs.microsoft.com/en-us/powershell/azure/create-azure-service-principal-azureps?view=azurermps-5.0.0&quot;&gt;Azure Powershell&lt;/a&gt;, though you can also use the Azure portal.&lt;/p&gt;
&lt;p&gt;The script below has been adapted from the &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/azure/dotnet-sdk-azure-authenticate&quot;&gt;Azure authentication for dotnet SDK article&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We&#39;re granting ourselves the right to modify a single resource group in the example below - if you want the app to modify Event Hub namespaces in multiple resource groups you need to grant the &#39;Scaler&#39; app Owner rights on each resource group&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$subscriptionName&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;your subscription name&gt;&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$appDisplayName&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;Scaler&quot;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# This can be anything you want&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$appPassword&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;a strong password for your app&gt;&quot;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$resourceGroupName&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;resource group containing your event hub&gt;&quot;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Login-AzureRmAccount &lt;span class=&quot;token comment&quot;&gt;# You&#39;ll be prompted to login&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Select-AzureRmSubscription&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;SubscriptionName &lt;span class=&quot;token variable&quot;&gt;$subscriptionName&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$sp&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;New-AzureRmADServicePrincipal&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;DisplayName &lt;span class=&quot;token variable&quot;&gt;$appDisplayName&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Password &lt;span class=&quot;token variable&quot;&gt;$appPassword&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;New-AzureRmRoleAssignment&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ServicePrincipalName &lt;span class=&quot;token variable&quot;&gt;$sp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ApplicationId &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;RoleDefinitionName Owner &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ResourceGroupName &lt;span class=&quot;token variable&quot;&gt;$resourceGroupName&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$sp&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Select&lt;/span&gt; DisplayName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ApplicationId &lt;span class=&quot;token comment&quot;&gt;# You&#39;ll need the ApplicationId later&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While you&#39;re connected you can use the following command to get the values you need for TenantId and SubscriptionId:&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;Get-AzureRmSubscription&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Select-Object&lt;/span&gt; SubscriptionId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; TenantId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; SubscriptionName&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;configuring-the-app-for-local-testing&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/#configuring-the-app-for-local-testing&quot;&gt;Configuring the app for local testing&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Clone the application from the &lt;a href=&quot;https://github.com/taddison/ScaleDownEventHubs/tree/aedbb76b40c0acd9a5a9bb952280f4d6e614093e&quot;&gt;ScaleDownEventHubs repository&lt;/a&gt; on GitHub.&lt;/p&gt;
&lt;p&gt;Once cloned modify your &lt;em&gt;local.settings.json&lt;/em&gt; file to look like the example file, using the values you acquired in the previous steps.&lt;/p&gt;
&lt;p&gt;Your &lt;strong&gt;ClientId&lt;/strong&gt; is the ApplicationId, and the &lt;strong&gt;ClientSecret&lt;/strong&gt; is the password you used.&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;IsEncrypted&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;Values&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;AzureWebJobsStorage&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;your connection string&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;AzureWebJobsDashboard&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;your connection string&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;ClientId&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;your client id&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;ClientSecret&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;your client secret&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;TenantId&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;your tenant id&gt;&quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can now add one or more event hub namespaces to the function (in &lt;em&gt;ScaleDown.cs&lt;/em&gt;).&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; namespaces &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;List&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;EventhubNamespace&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;EventhubNamespace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;subscription id&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;resource group&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;namespace&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the function runs it will use the credentials from your &lt;em&gt;local.settings.json&lt;/em&gt; file to compare each namespace against the target capacity (the last argument - 1 in the example above), and if the namespace has a higher throughput it will reduce it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In the portal you&#39;ll see reference to Throughput or Throughput Units - in the SDK this is captured by the Capacity property. For more information on the library we&#39;re using to manage the Event Hubs you can read the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/event-hubs/event-hubs-management-libraries&quot;&gt;Event Hubs management libraries&lt;/a&gt; documentation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To test this without making any changes you can comment out the update line, or set the capacity numbers to 20 (if the target capacity is higher than the current capacity it will not attempt to scale up - that is what auto-inflate is for!).&lt;/p&gt;
&lt;p&gt;By default the function is set to run once a day at 01:30. For testing purposes you&#39;ll probably want to change it to run every minute.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Default - daily at 01:30&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;TimerTrigger&lt;/span&gt;&lt;span class=&quot;token attribute-arguments&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;0 30 1 * * *&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TimerInfo&lt;/span&gt; myTimer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TraceWriter&lt;/span&gt; log&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// For testing - every minute&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;TimerTrigger&lt;/span&gt;&lt;span class=&quot;token attribute-arguments&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;* */1 * * * *&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;TimerInfo&lt;/span&gt; myTimer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TraceWriter&lt;/span&gt; log&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For more examples of how to specify the frequency see the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-timer&quot;&gt;timer documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you run the example locally you should see output similar to the below, where we can see our namespace is currently at 5 Throughput units, so we take no action (as the target was 20).&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/ptv3jEt_Yf-295.avif 295w, https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/ptv3jEt_Yf-590.avif 590w, https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/ptv3jEt_Yf-885.avif 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/ptv3jEt_Yf-295.webp 295w, https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/ptv3jEt_Yf-590.webp 590w, https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/ptv3jEt_Yf-885.webp 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/ptv3jEt_Yf-295.jpeg 295w, https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/ptv3jEt_Yf-590.jpeg 590w, https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/ptv3jEt_Yf-885.jpeg 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Function app output&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/ptv3jEt_Yf-295.jpeg&quot; width=&quot;885&quot; height=&quot;105&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h2 id=&quot;deploying-the-app&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/#deploying-the-app&quot;&gt;Deploying the app&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Publishing the app from Visual Studio will only deploy the function - by default the settings won&#39;t get deployed. You can either use the function CLI to deploy your settings, or add them in the portal. The portal will already have the storage account settings, so the key settings you need to add are &lt;strong&gt;ClientId&lt;/strong&gt;, &lt;strong&gt;TenantId&lt;/strong&gt; and &lt;strong&gt;ClientSecret&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Once published the app will start executing on the timer you have specified.&lt;/p&gt;
&lt;p&gt;You can monitor execution through the function app logs, Application Insights (if you add the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-functions/functions-monitoring&quot;&gt;required integration&lt;/a&gt;), or the &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/monitoring-and-diagnostics/monitoring-overview-activity-logs&quot;&gt;Azure activity log&lt;/a&gt;. Every time the capacity of the namespace is changed there will be entries in the operational logs.&lt;/p&gt;
</description>
      <pubDate>Sun, 10 Dec 2017 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2017/12/auto-deflating-event-hubs-with-a-function-app/</guid>
    </item>
    <item>
      <title>Using the SQLChecks library for SQL Server configuration management</title>
      <link>https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/</link>
      <description>&lt;p&gt;Getting started with configuration management can be pretty daunting. The number of frameworks/tools and their complexity can end up being a significant barrier to actually getting started (requiring you to answer both &#39;which framework should I use&#39; and then &#39;how do I actually use it&#39;). The key to making progress is to start small and make configuration management part of your daily/weekly checks.&lt;/p&gt;
&lt;p&gt;To this end I built the &lt;a href=&quot;https://github.com/taddison/SQLChecks&quot;&gt;SQLChecks&lt;/a&gt; library to make it easy to get started checking your SQL Server configuration, and start implementing the patterns that the bigger frameworks will require.&lt;/p&gt;
&lt;p&gt;This post will walk through how to set up a basic configuration management process to record and check the state of your SQL Server instances using &lt;a href=&quot;https://github.com/pester/Pester&quot;&gt;Pester&lt;/a&gt; and SQLChecks. Once built you can run it interactively (reporting to the console as shown below), or as part of a schedule to produce a report.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/ZPtVdihZbZ-295.avif 295w, https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/ZPtVdihZbZ-590.avif 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/ZPtVdihZbZ-295.webp 295w, https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/ZPtVdihZbZ-590.webp 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/ZPtVdihZbZ-295.jpeg 295w, https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/ZPtVdihZbZ-590.jpeg 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Pester Tests&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/ZPtVdihZbZ-295.jpeg&quot; width=&quot;590&quot; height=&quot;180&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;In this example we&#39;ve failed one of the tests that is checking that the right trace flags are set on the server (and both of them are missing!)&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;specifying-your-desired-instance-configuration&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/#specifying-your-desired-instance-configuration&quot;&gt;Specifying your desired instance configuration&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One of the key activities to onboard a server/service into configuration management is to specify what that configuration should look like. SQLChecks currently focuses on SQL instance configuration, and at the time of writing allows us to specify:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The value of any sp_configure configuration values&lt;/li&gt;
&lt;li&gt;Any global trace flags (including checking that there are no global trace flags)&lt;/li&gt;
&lt;li&gt;The number of SQL Server error logs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For each server you want to check you&#39;ll need a file that describes the instance configuration. Only the &lt;strong&gt;ServerInstance&lt;/strong&gt; property is mandatory - everything else can be omitted, as only the listed properties will be checked. By convention this file should be named instance.config.json.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;localhost.config.json&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;ServerInstance&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;localhost&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;SpConfig&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;MaxDegreeOfParallelism&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;XpCmdShellEnabled&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;ShowAdvancedOptions&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;NumErrorLogs&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;14&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;TraceFlags&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3226&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;7412&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The SpConfig property is actually a collection of properties, and you can discover all the available options (as well as their current values) by executing the following command against one of your servers. This requires you have the &lt;a href=&quot;https://dbatools.io/&quot;&gt;DBATools&lt;/a&gt; module installed.&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;Get-DbaSpConfigure&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;SqlInstance localhost &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Out-GridView&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/ZOn1dKodc8-295.avif 295w, https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/ZOn1dKodc8-590.avif 590w, https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/ZOn1dKodc8-885.avif 885w, https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/ZOn1dKodc8-1180.avif 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/ZOn1dKodc8-295.webp 295w, https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/ZOn1dKodc8-590.webp 590w, https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/ZOn1dKodc8-885.webp 885w, https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/ZOn1dKodc8-1180.webp 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/ZOn1dKodc8-295.jpeg 295w, https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/ZOn1dKodc8-590.jpeg 590w, https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/ZOn1dKodc8-885.jpeg 885w, https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/ZOn1dKodc8-1180.jpeg 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;SpConfigure Options&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/ZOn1dKodc8-295.jpeg&quot; width=&quot;1180&quot; height=&quot;317&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The &lt;strong&gt;ConfigName&lt;/strong&gt; is what you need to use in your SpConfig section. For every value you want to check, list the ConfigName and value you expect.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You can put these text files anywhere, though I would suggest creating a private git repository for them. This will allow you to keep a history of your configuration changes, and require changes to be committed along with any explanations that may help later debugging (e.g. &#39;Adding TF3459 to benchmark redo performance for a week&#39;). Changing the values first in source and then on the server is a great habit to get into (especially if you later leverage more fully-featured frameworks).&lt;/p&gt;
&lt;p&gt;Once you have your config file(s) you&#39;re ready to see if reality matches the plan.&lt;/p&gt;
&lt;h2 id=&quot;installing-sqlchecks&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/#installing-sqlchecks&quot;&gt;Installing SQLChecks&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;SQLChecks requires DBATools, so if you&#39;ve not already you need to &lt;a href=&quot;https://dbatools.io/download/&quot;&gt;install that&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once you&#39;ve got DBATools you&#39;ll need to clone SQLChecks (you can also &lt;a href=&quot;https://github.com/taddison/SQLChecks/archive/master.zip&quot;&gt;download a zip from Github&lt;/a&gt;). Once cloned you need to import the SQLChecks module.&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;c:&#92;src&gt;git clone https:&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;github&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;com/taddison/SQLChecks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;git&lt;br /&gt;c:&#92;src&gt;&lt;span class=&quot;token function&quot;&gt;Import-Module&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&#92;SQLChecks&#92;src&#92;SQLChecks &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Force &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Verbose&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&#39;re now ready to &#39;test&#39; our configs - to make this easier I&#39;d suggest creating a PowerShell script that will test one or more of your files. Examples of these can be found in the &lt;a href=&quot;https://github.com/taddison/SQLChecks/tree/master/examples&quot;&gt;SQLChecks repo&lt;/a&gt; - included below is a file that will test a single config.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that we&#39;re referencing a folder we cloned as part of SQLChecks (c:&#92;src&#92;SQLChecks&#92;tests), as well as a configuration file (localhost.config.json) - update the names/paths accordingly.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;RunChecks.ps1&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;#Requires -Modules DBATools, SQLChecks&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token namespace&quot;&gt;[string]&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$data&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-Content&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Path &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&#92;localhost&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;json &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Raw&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$data&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ConvertFrom-Json&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;OutVariable configs &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Out-Null&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Invoke-Pester&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Script @&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;Path=&lt;span class=&quot;token string&quot;&gt;&#39;c:&#92;src&#92;SQLChecks&#92;tests&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;Parameters= @&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;configs=&lt;span class=&quot;token variable&quot;&gt;$configs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can then run the checks by executing the PowerShell file:&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;c:&#92;src&#92;SQLInstanceConfigs&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&#92;RunChecks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ps1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you&#39;ve configured everything correctly you should see Pester start to run tests against your server.&lt;/p&gt;
&lt;h2 id=&quot;what-is-happening&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/#what-is-happening&quot;&gt;What is happening&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The high level flow for what happens when you execute RunChecks.ps1 is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Checks to see if the DBATools and SQLChecks modules are available&lt;/li&gt;
&lt;li&gt;Extract the contents of the specified file (localhost.config.json) to a variable called $data, using -Raw as we want it as a whole string rather than as a collection of lines (which we&#39;d get by default)&lt;/li&gt;
&lt;li&gt;Convert the $data string to Json, and store the result in a variable called $configs. We use Out-Null to suppress console output&lt;/li&gt;
&lt;li&gt;Run Pester and complete all the tests (files with .tests.ps1 extensions) in the specified folder (c:&#92;src&#92;SQLChecks&#92;tests), passing the $configs parameter to each test file&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If we&#39;re executing tests against a folder full of config files the main change is that we&#39;re looping through files to extract configs (and then testing all of them), rather than testing a single file.&lt;/p&gt;
&lt;p&gt;Right now there is only a single test file in the tests folder, which contains multiple tests for the SQL Instance. In the future you can expect to see files which perform tests against availability groups, databases, and Windows server.&lt;/p&gt;
&lt;h2 id=&quot;generating-reports&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/#generating-reports&quot;&gt;Generating reports&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In order to generate a report we&#39;re going to leverage &lt;a href=&quot;https://github.com/equelin/Format-Pester&quot;&gt;Format-Pester&lt;/a&gt; to run all of our tests, and output them into a self-contained HTML report. This can then be saved for future reference or distributed via email/fileshare/etc.&lt;/p&gt;
&lt;p&gt;You&#39;ll need to install both &lt;a href=&quot;https://github.com/equelin/Format-Pester&quot;&gt;Format-Pester&lt;/a&gt; and &lt;a href=&quot;https://github.com/iainbrighton/PScribo&quot;&gt;PScribo&lt;/a&gt; (used by Format-Pester) to execute this script.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GenerateReport.ps1&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;#Requires -Modules DBATools, SQLChecks, Format-Pester, PScribo&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$instances&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-ChildItem&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Path c:&#92;src&#92;SQLInstanceConfigs &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Filter&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;json&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$configs&lt;/span&gt; = @&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$instance&lt;/span&gt; in &lt;span class=&quot;token variable&quot;&gt;$instances&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token namespace&quot;&gt;[string]&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$configData&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-Content&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Path &lt;span class=&quot;token variable&quot;&gt;$instance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PSPath &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Raw&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$configData&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ConvertFrom-Json&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;OutVariable &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;configs&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Invoke-Pester&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Script @&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;Path=&lt;span class=&quot;token string&quot;&gt;&#39;c:&#92;src&#92;SQLChecks&#92;tests&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;Parameters= @&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;configs=&lt;span class=&quot;token variable&quot;&gt;$configs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;PassThru &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Format-Pester&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Format HTML &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Path c:&#92;reports&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;This example pulls all configuration files from the specified folder (c:&#92;src&#92;SQLInstanceConfigs) and outputs the report to a target folder (c:&#92;reports)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;when-configs-dont-match-reality&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/#when-configs-dont-match-reality&quot;&gt;When configs don&#39;t match reality&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A key part of configuration management is dealing with settings that don&#39;t match their configuration value. If you run your tests and discover that the MAXDOP setting isn&#39;t correct you might want that to be automatically handled. This isn&#39;t something that this solution handles (though you could easily extend it to do so!).&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.microsoft.com/en-us/powershell/scripting/dsc/overview/overview&quot;&gt;DSC&lt;/a&gt; has a very simple (yet powerful) model for thinking about configuration management that helps highlight the core difference between this solution and what I&#39;d consider &#39;full&#39; configuration management. Every resource in DSC (which could be something like an sp_configure value) supports three operations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Get the value&lt;/li&gt;
&lt;li&gt;Test the value against config&lt;/li&gt;
&lt;li&gt;Set the value to something&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can see that we&#39;ve got something that deals with the first two (get, test) only.&lt;/p&gt;
&lt;p&gt;Though it might seem we&#39;re missing out on the most important part of configuration management by not implementing the &#39;set&#39; functionality, that actually sits at the top of the pyramid. Knowing what your server configuration is supposed to look like and being able to verify it does or does not match are definitely where you should be starting.&lt;/p&gt;
&lt;h2 id=&quot;further-reading&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/#further-reading&quot;&gt;Further reading&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For a SQL specific solution to the problem of configuration you can look at &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/relational-databases/policy-based-management/administer-servers-by-using-policy-based-management&quot;&gt;Policy Based Management&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For something that supports the full set of operations (get, test, and set) I&#39;d suggest starting with &lt;a href=&quot;https://docs.microsoft.com/en-us/powershell/dsc/overview&quot;&gt;DSC&lt;/a&gt;. There are some promising developments in &lt;a href=&quot;https://github.com/Microsoft/ReverseDSC&quot;&gt;ReverseDSC&lt;/a&gt; that might make getting started with DSC for SQL Server much easier.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://puppet.com/docs/puppet/5.3/architecture.html&quot;&gt;Puppet&lt;/a&gt; and &lt;a href=&quot;https://docs.chef.io/chef_overview.html&quot;&gt;Chef&lt;/a&gt; are also worth looking at - particularly the similarities and differences between their architectures (though the implementations differ Puppet, Chef, and DSC have a lot in common).&lt;/p&gt;
&lt;p&gt;Finally, some books I&#39;d recommend on engineering operations (including Configuration Management falls) are &lt;a href=&quot;https://www.amazon.co.uk/Web-Operations-Keeping-Data-Time/dp/1449377440&quot;&gt;Web Operations: Keeping the Data On Time&lt;/a&gt; and &lt;a href=&quot;https://landing.google.com/sre/book.html&quot;&gt;Site Reliability Engineering&lt;/a&gt;.&lt;/p&gt;
</description>
      <pubDate>Sun, 03 Dec 2017 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/</guid>
    </item>
    <item>
      <title>Simplifying alert configuration in the OMS to Slack function app</title>
      <link>https://tjaddison.com/blog/2017/11/simplifying-alert-configuration-in-the-oms-to-slack-function-app/</link>
      <description>&lt;p&gt;In the &lt;a href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions&quot;&gt;last post&lt;/a&gt; we had a fairly complete (if limited) solution for the routing of arbitrary OMS metric alerts to Slack. After having used this in production for a while I can report that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When configured correctly it works well (we get the right alerts in the right places)&lt;/li&gt;
&lt;li&gt;Explaining how to configure a new alert is extraordinarily difficult&lt;/li&gt;
&lt;li&gt;There is a seemingly arbitrary separation of alert configuration (some in the OMS webhook payload, some in the function app itself)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I was planning on extracting the configuration from the application, and rather than extract as-is I&#39;ve made changes that will hopefully address the last two points.&lt;/p&gt;
&lt;p&gt;Inspired by a recent &lt;a href=&quot;https://www.hanselman.com/blog/CloudDatabaseNoSQLNahJustUseCSVsAndCsvHelper.aspx&quot;&gt;Hanselman post&lt;/a&gt; I&#39;ve opted to use CSVs and CSVHelper to store the alert configuration data. The tabular format and lack of references between config files leant itself to this task, and I wanted to start with a format that ensured it was easy to source control the configuration (so they can live and be versioned with the project).&lt;/p&gt;
&lt;p&gt;If you want to look at the finished repo you can see it &lt;a href=&quot;https://github.com/taddison/blog-oms-to-slack/tree/master/GenericFunctionExternalConfig&quot;&gt;on GitHub&lt;/a&gt; - the rest of the post will focus on the key changes to configuring an alert, as well as how I&#39;ve implemented those changes.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/11/simplifying-alert-configuration-in-the-oms-to-slack-function-app/0UV_5UQMln-295.avif 295w, https://tjaddison.com/blog/2017/11/simplifying-alert-configuration-in-the-oms-to-slack-function-app/0UV_5UQMln-590.avif 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/11/simplifying-alert-configuration-in-the-oms-to-slack-function-app/0UV_5UQMln-295.webp 295w, https://tjaddison.com/blog/2017/11/simplifying-alert-configuration-in-the-oms-to-slack-function-app/0UV_5UQMln-590.webp 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2017/11/simplifying-alert-configuration-in-the-oms-to-slack-function-app/0UV_5UQMln-295.jpeg 295w, https://tjaddison.com/blog/2017/11/simplifying-alert-configuration-in-the-oms-to-slack-function-app/0UV_5UQMln-590.jpeg 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Excel Configuration&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/11/simplifying-alert-configuration-in-the-oms-to-slack-function-app/0UV_5UQMln-295.jpeg&quot; width=&quot;590&quot; height=&quot;59&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h2 id=&quot;configuring-an-alert&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/11/simplifying-alert-configuration-in-the-oms-to-slack-function-app/#configuring-an-alert&quot;&gt;Configuring an alert&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One of the most significant changes is the OMS payload. I&#39;ve included the old &amp;amp; new payloads for a CPU alert below.&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Old&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;IncludeSearchResults&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;WarningThreshold&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.35&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;CriticalThreshold&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.45&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;ValueMultiplier&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.01&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;Channel&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#oms-alerts&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;LessThanThresholdIsBad&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;AlertMessage&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Infra - CPU&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;MetricName&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Processor Usage %&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;FormatString&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;P0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;ObservationThreshold&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// New&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;IncludeSearchResults&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;MetricName&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Processor Usage %&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The OMS queries themselves remain unchanged.&lt;/p&gt;
&lt;p&gt;The only additional piece of information we provide in the payload (IncludeSearchResults is needed to get OMS to send the metrics to our function) is the &lt;em&gt;MetricName&lt;/em&gt;, and this acts as the key which maps to the configuration data in our app. The &lt;em&gt;MetricName&lt;/em&gt; in the OMS payload needs to match the &lt;em&gt;MetricName&lt;/em&gt; in all configuration CSVs.&lt;/p&gt;
&lt;p&gt;The configuration data for each alert requires a minimum of a &lt;em&gt;DefaultAlertConfig&lt;/em&gt; and a &lt;em&gt;DefaultAlertNotificationConfig&lt;/em&gt; file. These are CSVs and are stored in the Configuration directory of the application (these must have their CopyToOutputDirectory set to CopyIfNewer to ensure they get deployed with the app).&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;DefaultAlertConfig&lt;/em&gt; file contains the details needed to convert metrics from OMS (the &lt;em&gt;ValueMultiplier&lt;/em&gt;), as well as the default alerting thresholds.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;As a reminder the &lt;em&gt;ValueMultiplier&lt;/em&gt; is needed to convert values which might be reported as whole numbers in OMS (e.g. 99% is reported as 99) into values appropriate for formatting (if we want to format as P0 we need the value to be 0.99). The value post-multiplication is also used when comparing to any alert thresholds.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One column which was significantly reworked is &#39;minimum violations to alert&#39; - which is hopefully better named than its previous incarnation &#39;ObservationThreshold&#39;. If in our example CSV below the processor query had 5 data points (e.g. per-minute over 5 minutes), at least 3 would need to be at or above 35% to trigger a warning alert (and 3 would need to be at or above 50% for critical).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;defaultAlertConfig.csv&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;metricName,warningThreshold,criticalThreshold,lessThanThresholdIsBad,minimumViolationsToAlert,valueMultiplier
Processor Usage %,0.35,0.5,false,3,0.01
Free Space %,0.2,0.1,true,1,0.01
Free Megabytes,10000,5000,true,1,1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The DefaultAlertNotificationConfig file is used to determine how to format the alert, the message that should be used, and the default channel to route the alert to.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;defaultAlertNotificationConfig.csv&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;metricName,channel,formatString,alertMessage
Processor Usage %,#alerts,P0,Infra - CPU
Free Space %,#alerts,P0,Infra - Drive
Free Megabytes,#alerts,N0,Infra - Memory
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;customising-alert-thresholds-or-routing&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/11/simplifying-alert-configuration-in-the-oms-to-slack-function-app/#customising-alert-thresholds-or-routing&quot;&gt;Customising alert thresholds or routing&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Setting per-server rules for alerts is now done by adding records to the &lt;em&gt;OverrideAlertConfig&lt;/em&gt; CSV file, which has the following format:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;overrideAlertConfig.csv&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;metricName,machineName,warningThreshold,criticalThreshold,minimumViolationsToAlert
Processor Usage %,Server 2,0.3,0.4,
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Metric and Machine are both mandatory, and every other field is optional. The way the above record reads is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For Processor Usage % on Server 2&lt;/li&gt;
&lt;li&gt;Override warning to 30%&lt;/li&gt;
&lt;li&gt;Override critical to 40%&lt;/li&gt;
&lt;li&gt;Inherit the minimum violations to alert from the default&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For routing the configuration file is &lt;em&gt;OverrideAlertNotificationConfig&lt;/em&gt;, and it allows us to override based on either metric, machine, or both.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;overrideAlertNotificationConfig.csv&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;metricName,machineName,channel
,Server1,#Server1Team
Free Space %,,#memory-monitors
Processor Usage %,Server2,#server2-cpu
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example you can see we have one rule that applies to a whole server, one that applies to a metric, and one which is targeted at a specific combination of a server and a metric.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The current behaviour for routing is that an exact match (metric + machine) will replace the default channel, and a partial match will add a channel to the list.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;reading-configuration&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/11/simplifying-alert-configuration-in-the-oms-to-slack-function-app/#reading-configuration&quot;&gt;Reading configuration&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://joshclose.github.io/CsvHelper/&quot;&gt;CSVHelper&lt;/a&gt; library was used to read CSV files, and this required minimal code. Using the out of the box defaults was all that was required to build the config population methods. The example below will read the CSV file using the headers (note these are case sensitive) and call the constructor to build each object.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// OverrideAlertNotificationConfig.cs&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;OverrideAlertNotificationConfig&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;OverrideAlertNotificationConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; metricName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; machineName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; channel &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        MetricName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;metricName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; metricName&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        MachineName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;machineName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; machineName&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        Channel &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; channel&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; MetricName &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; MachineName &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; Channel &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// ConfigHelper.cs&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;List&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;OverrideAlertNotificationConfig&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;GetOverrideAlertNotificationConfigs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; tr &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; File&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;OpenText&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;_context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;FunctionAppDirectory &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/Configuration/overrideAlertNotificationConfig.csv&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; csv &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;CsvReader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tr&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; csv&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;GetRecords&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;OverrideAlertNotificationConfig&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ToList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The _context object is a Microsoft.Azure.WebJobs.ExecutionContext and is needed to get the root folder of our function app deployment (as we&#39;re deploying the configuration files along with the function app we need to get at them). More information can be found on the &lt;a href=&quot;https://github.com/Azure/azure-webjobs-sdk-script/wiki/Retrieving-information-about-the-currently-running-function&quot;&gt;WebJobs Github wiki&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The defaults of CSVHelper automatically map an empty string in our CSV to null, so we don&#39;t have any additional configuration/maps required.&lt;/p&gt;
&lt;p&gt;One area that does need some attention on the function app is reading/tolerating malformed CSVs. Right now if you get part of the CSV wrong, the whole thing fails.&lt;/p&gt;
&lt;h2 id=&quot;using-configuration&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/11/simplifying-alert-configuration-in-the-oms-to-slack-function-app/#using-configuration&quot;&gt;Using configuration&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As we now have the configuration isolated in a ConfigHelper class we&#39;re able to make the AlertProcessor a little simpler. The start of the ProcessAlert method (that deals with determining whether or not an alert sent from OMS should actually be considered an alert) now looks like this:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; alertConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; _configHelper&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;GetAlertConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;alert&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Is this a &amp;lt; or &gt; alert?&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; comparison &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;alertConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;LessThanThresholdIsBad &lt;span class=&quot;token punctuation&quot;&gt;?&lt;/span&gt;&lt;/span&gt; LessThan &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; MoreThan&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Aggregate metrics to produce a single summary record&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; processedValues &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; alert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MetricValues&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mv &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; mv&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Value &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; alertConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ValueMultiplier&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; totals &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    Average &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; processedValues&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Average&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;Min &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; processedValues&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Min&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;Max &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; processedValues&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Max&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;Critical &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; processedValues&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;comparison&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; alertConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CriticalThreshold&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;Warning &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; processedValues&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;comparison&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; alertConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;WarningThreshold&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Determine alert criticality&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; isWarning &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; totals&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Warning &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; alertConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MinimumViolationsToAlert&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; isCritical &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; totals&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Critical &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; alertConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MinimumViolationsToAlert&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// If the alert doesn&#39;t cross the warning threshold return&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;isWarning&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The final part of the method has also been simplified, so we&#39;re now getting closer to truly separating the alert logic from the notification logic:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Where should the alert go&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; notificationConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; _configHelper&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;GetAlertNotificationConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;alert&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Build message&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// ...elided&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// Send message&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; channel &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; notificationConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Channels&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; SlackHelper&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;SendSlackMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;channel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;other-changes&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/11/simplifying-alert-configuration-in-the-oms-to-slack-function-app/#other-changes&quot;&gt;Other changes&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In order to keep the alerts as terse as possible stripping the domain name from a machine proved useful, so rather than seeing Server1.foo.corp was alerting we&#39;d just see Server1. This is applied in function called by OMS, and so the configuration can refer to machines by hostname only.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;machineName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; machineName&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.foo.corp&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Empty&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ToLower&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also put the machine name into lowercase, and ensure when the configs are constructed that we convert all the servernames to lowercase too.&lt;/p&gt;
</description>
      <pubDate>Wed, 08 Nov 2017 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2017/11/simplifying-alert-configuration-in-the-oms-to-slack-function-app/</guid>
    </item>
    <item>
      <title>Getting funnel metrics right with Power BI when applying filters</title>
      <link>https://tjaddison.com/blog/2017/10/getting-funnel-metrics-right-with-power-bi-when-applying-filters/</link>
      <description>&lt;p&gt;In a &lt;a href=&quot;https://tjaddison.com/blog/2017/09/creating-recruitment-funnel-metrics-in-power-bi&quot;&gt;previous post&lt;/a&gt; we built a simple set of recruitment funnel metrics that allowed us to plot the hiring funnel from a new application to (hopefully) a hire. The core measure we built was &lt;em&gt;inclusive applications&lt;/em&gt;, which works fine until you start to filter your data, after which point you go from a set of data like this:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/10/getting-funnel-metrics-right-with-power-bi-when-applying-filters/X2pUjp8A3Q-295.avif 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/10/getting-funnel-metrics-right-with-power-bi-when-applying-filters/X2pUjp8A3Q-295.webp 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Initial Data Model&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/10/getting-funnel-metrics-right-with-power-bi-when-applying-filters/X2pUjp8A3Q-295.jpeg&quot; width=&quot;295&quot; height=&quot;137&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;To a funnel that looks like this when filtered for the DBA role:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/10/getting-funnel-metrics-right-with-power-bi-when-applying-filters/7iPcVNNxNW-295.avif 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/10/getting-funnel-metrics-right-with-power-bi-when-applying-filters/7iPcVNNxNW-295.webp 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Wrong Funnel&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/10/getting-funnel-metrics-right-with-power-bi-when-applying-filters/7iPcVNNxNW-295.jpeg&quot; width=&quot;295&quot; height=&quot;167&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Which is kind of odd as we&#39;d like to reflect the fact we phone screened two DBAs.&lt;/p&gt;
&lt;p&gt;The reason the formula stops working when we filter on the role (DBA) the table we&#39;re working on only has 3 rows in, none of which have the Phone stage. Although it seems fairly contrived in our example dataset, once you start to slice the data on multiple dimensions it can be easy to end up in a place where you have nobody in a certain stage, particularly latter stages like Offer/Hire (for example: What did the funnel look like for DBA candidates in London got past the Phone Screen in Q1 17?).&lt;/p&gt;
&lt;p&gt;The good news is this is an easy fix.&lt;/p&gt;
&lt;h2 id=&quot;fixing-the-funnel&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/10/getting-funnel-metrics-right-with-power-bi-when-applying-filters/#fixing-the-funnel&quot;&gt;Fixing the funnel&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Solving this issue is much easier if we first improve our data model, and take the &#39;Stage&#39; dimension and create a dedicated dimension (or lookup) table for it. A great introduction to data modelling is available on &lt;a href=&quot;https://powerpivotpro.com/2016/02/data-modeling-power-pivot-power-bi/&quot;&gt;PowerPivotPro&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/10/getting-funnel-metrics-right-with-power-bi-when-applying-filters/lsKjyH4eaV-295.avif 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/10/getting-funnel-metrics-right-with-power-bi-when-applying-filters/lsKjyH4eaV-295.webp 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Updated Data Model&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/10/getting-funnel-metrics-right-with-power-bi-when-applying-filters/lsKjyH4eaV-295.jpeg&quot; width=&quot;295&quot; height=&quot;96&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Now we need to update our function to reference the dimension table (which as an added benefit simplifies the formula, as now we can use All(Table) rather than having to enumerate the columns):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Corrected Inclusive Applications =
CALCULATE (
    [Applications],
    FILTER (
        ALL (Stages),
        Stages[StageOrder] &amp;gt;= MAX ( Stages[StageOrder] )
    )
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gives us the chart we expect:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/10/getting-funnel-metrics-right-with-power-bi-when-applying-filters/xtsqqCQI-Z-295.avif 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/10/getting-funnel-metrics-right-with-power-bi-when-applying-filters/xtsqqCQI-Z-295.webp 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Correct Funnel&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/10/getting-funnel-metrics-right-with-power-bi-when-applying-filters/xtsqqCQI-Z-295.jpeg&quot; width=&quot;295&quot; height=&quot;164&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;You can download a workbook with the data in &lt;a href=&quot;https://tjaddison.com/blog/2017/10/getting-funnel-metrics-right-with-power-bi-when-applying-filters/FunnelSample.pbix&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</description>
      <pubDate>Tue, 31 Oct 2017 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2017/10/getting-funnel-metrics-right-with-power-bi-when-applying-filters/</guid>
    </item>
    <item>
      <title>Troubleshooting an SSRS slowdown - portal delays, PREEMPTIVE_OS_LOOKUPACCOUNTSID, and bad plans</title>
      <link>https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/</link>
      <description>&lt;p&gt;&lt;em&gt;This tale takes place on an SSRS 2016 Enterprise instance running on Server 2012 R2.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-report-server-is-slow&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/#the-report-server-is-slow&quot;&gt;The Report Server is slow&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Users had reported that the SSRS instance was &#39;slow&#39;, and after opening the portal I could see what they meant. In addition to the home page taking a very long time to load (minutes), sometimes after loading I&#39;d be presented with a menu bar and no folders/reports (despite knowing I should have seen a bunch of folders and reports).&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/amVZU23aY9-295.avif 295w, https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/amVZU23aY9-590.avif 590w, https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/amVZU23aY9-885.avif 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/amVZU23aY9-295.webp 295w, https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/amVZU23aY9-590.webp 590w, https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/amVZU23aY9-885.webp 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/amVZU23aY9-295.jpeg 295w, https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/amVZU23aY9-590.jpeg 590w, https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/amVZU23aY9-885.jpeg 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Loading&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/amVZU23aY9-295.jpeg&quot; width=&quot;885&quot; height=&quot;252&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;The first port of call was the execution catalog, to see if any reports were running at all (as well as interactive rendering we have subscriptions and rendering happening via the API).&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt;      &lt;span class=&quot;token keyword&quot;&gt;top&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TimeStart&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ItemAction&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ItemPath&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Status&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt;        ReportServer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ExecutionLog3 &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; el&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;by&lt;/span&gt;    el&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TimeStart &lt;span class=&quot;token keyword&quot;&gt;desc&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The query showed that subscriptions were working and some users were successfully browsing &amp;amp; running reports interactively. Knowing that the whole instance wasn&#39;t broken I was able to proceed to troubleshoot the specific issue of the portal misbehaving.&lt;/p&gt;
&lt;h2 id=&quot;looking-into-the-portal-issues&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/#looking-into-the-portal-issues&quot;&gt;Looking into the portal issues&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To summarise the problem after a few minutes of sense checks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The portal is very slow to load (no errors observed in the browser console/no 404s/etc.)&lt;/li&gt;
&lt;li&gt;Sometimes after the portal loads there are no reports/folders visible&lt;/li&gt;
&lt;li&gt;Looking at user/folder settings (e.g. security) everything appears normal (permissions are intact, management interface works)&lt;/li&gt;
&lt;li&gt;The report API is unaffected (can browse folders + render reports quickly and without any issues by navigating to /reportserver instead of /reports)&lt;/li&gt;
&lt;li&gt;The servers hosting SSRS and the ReportServer database are not under high load (not capped on CPU/Memory/free drive space)&lt;/li&gt;
&lt;li&gt;No configuration changes had been made recently&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The portal looked to be the problem, so the next stop was the error logs. You can read more information about these logs online (&lt;a href=&quot;https://docs.microsoft.com/en-us/sql/reporting-services/report-server/report-server-service-trace-log&quot;&gt;Report Server Service Trace Log&lt;/a&gt;) - and in our case we&#39;re after the portal log file (Microsoft.ReportingServices.Portal.WebHost). Browsing these logs is always a little painful - someone somewhere has always left a misconfigured report, a report that belongs to someone who is no longer with the company - and shame on me we don&#39;t put these logs somewhere centrally queryable (and as such the errors only get dealt with when an end user reports an issue).&lt;/p&gt;
&lt;p&gt;Ignoring all of the alerts which can get fixed later (subscriptions failing due to bad parameters, etc.), there were some unfamiliar alerts all of which have identical stack traces. The final error message in the trace was:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;An error occurred within the report server database. This may be due to a connection failure, timeout or low disk condition within the database.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Which was immediately preceded by:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;System.Data.SqlClient.SqlException: Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This tells us our next stop should be the ReportServer database. The call stack of the error indicated it was part of &lt;code&gt;GetFavoriteItems&lt;/code&gt; - which is why only the portal was having issues and not the API/reports themselves.&lt;/p&gt;
&lt;h2 id=&quot;reportserver-database&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/#reportserver-database&quot;&gt;ReportServer database&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Once we dropped into SQL the diagnosis was pretty easy.&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;exec&lt;/span&gt; sp_whoisactive &lt;span class=&quot;token variable&quot;&gt;@get_plans&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A few queries running for over a minute, with a wait type of &lt;a href=&quot;https://www.sqlskills.com/help/waits/preemptive_os_lookupaccountsid/&quot;&gt;PREEMPTIVE_OS_LOOKUPACCOUNTSID&lt;/a&gt;. The query in question was &lt;strong&gt;&lt;em&gt;dbo.GetAllFavoriteItems&lt;/em&gt;&lt;/strong&gt;, and the offending statement was:&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;&lt;br /&gt;    C&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    C&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PolicyID&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    SD&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;NtSecDescPrimary&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    C&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    C&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    C&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ItemID&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    DATALENGTH&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; C&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Content &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Size&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    C&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Description&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    C&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CreationDate&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    C&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ModifiedDate&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    SUSER_SNAME&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;CU&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Sid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    CU&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;UserName&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    SUSER_SNAME&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;MU&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Sid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    MU&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;UserName&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    C&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MimeType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    C&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ExecutionTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    C&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Hidden&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    C&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SubType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    C&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ComponentID&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    CAST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;bit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt;&lt;br /&gt;   Catalog &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; C&lt;br /&gt;   &lt;span class=&quot;token keyword&quot;&gt;INNER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;dbo&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Catalog&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; P &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; C&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ParentID &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; P&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ItemID&lt;br /&gt;   &lt;span class=&quot;token keyword&quot;&gt;INNER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;dbo&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Users&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; CU &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; C&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CreatedByID &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; CU&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UserID&lt;br /&gt;   &lt;span class=&quot;token keyword&quot;&gt;INNER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;dbo&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Users&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; MU &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; C&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ModifiedByID &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; MU&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UserID&lt;br /&gt;   &lt;span class=&quot;token keyword&quot;&gt;INNER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;dbo&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;Favorites&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; F &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; C&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ItemID &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; F&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ItemID &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; F&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UserID &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@UserID&lt;/span&gt;&lt;br /&gt;   &lt;span class=&quot;token keyword&quot;&gt;LEFT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;OUTER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;dbo&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;SecData&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; SD &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; C&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PolicyID &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; SD&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PolicyID &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; SD&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AuthType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@AuthType&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The wait type is coming from the calls into &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/t-sql/functions/suser-sname-transact-sql&quot;&gt;SUSER_SNAME&lt;/a&gt;. What this function does is takes the Sid and asks the local machine for the user name associated with it (by calling the &lt;a href=&quot;https://technet.microsoft.com/en-us/library/ff428139(v=ws.10).aspx#BKMK_LsaLookupSIDs&quot;&gt;LsaLookupSid&lt;/a&gt; function). If the mapping between Sid and user name isn&#39;t on the local machine, the machine will then ask the domain controller for the mapping.&lt;/p&gt;
&lt;p&gt;Running the following query took over 2 minutes:&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; suser_sname&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;u&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Sid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; dbo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Users &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; u&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The only wait type observed was PREEMPTIVE_OS_LOOKUPACCOUNTSID, and each wait was between 70 and 150 milliseconds. In this case there are quite a few users that belong to a domain with a DC is located on a different continent to the report server (and the wait times correlate with the round-trip time from server to DC).&lt;/p&gt;
&lt;p&gt;At this point I started looking at caching of the lookups (and it turns out there is a cache but only for 128 entries, and given we have 1000s of user records the cache will constantly get cycled if we are scanning the table). There is a registry setting to increase the cache size (after learning about the post from &lt;a href=&quot;https://stackoverflow.com/questions/31969101/ssrs-users-table&quot;&gt;this SO question&lt;/a&gt;, &lt;a href=&quot;https://support.microsoft.com/en-us/help/946358/the-lsalookupsids-function-may-return-the-old-user-name-instead-of-the&quot;&gt;this support article&lt;/a&gt;, and &lt;a href=&quot;https://msdn.microsoft.com/en-us/library/ms721799.aspx&quot;&gt;these docs&lt;/a&gt;), but before I went down that road I reflected that the DC hadn&#39;t moved overnight and the report server had been working fine up until today.&lt;/p&gt;
&lt;h2 id=&quot;plan-regression&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/#plan-regression&quot;&gt;Plan regression&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The problem (and the eventual solution) was the query plan. Looking again at the query it was clear we were pulling favourites for a single user, so we shouldn&#39;t be scanning the user table and looking up all the account name for everyone.&lt;/p&gt;
&lt;p&gt;The current plan was scanning the user table twice, and for every row coming from the user table it was evaluating the suser_sname function for every row (represented by the compute scalars in the image below). The reason we scan twice is the query returns the name of the last modified by user as well as the created by user (for a report).&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/yo-1vdba5P-295.avif 295w, https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/yo-1vdba5P-590.avif 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/yo-1vdba5P-295.webp 295w, https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/yo-1vdba5P-590.webp 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/yo-1vdba5P-295.jpeg 295w, https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/yo-1vdba5P-590.jpeg 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Bad Plan&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/yo-1vdba5P-295.jpeg&quot; width=&quot;590&quot; height=&quot;167&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Looking in the table which holds user favourites I could see there was only a small number of rows, and so it seemed probable there was a better plan (especially as the portal had been performing well until recently, and there was no user who had favourited every report on the server…).&lt;/p&gt;
&lt;p&gt;After running the procedure with a few users (including the user who had the most favourites) and getting only good (non-scan) plans I recompiled the procedure, and usual service was instantly returned (the query that had been running for minutes and timing out now finished in less than a second). The plan is as expected - get the favourites and then perform the Sid lookups only the users who own/modified one of the favourited reports.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/Oy-SPiRY08-295.avif 295w, https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/Oy-SPiRY08-590.avif 590w, https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/Oy-SPiRY08-885.avif 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/Oy-SPiRY08-295.webp 295w, https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/Oy-SPiRY08-590.webp 590w, https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/Oy-SPiRY08-885.webp 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/Oy-SPiRY08-295.jpeg 295w, https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/Oy-SPiRY08-590.jpeg 590w, https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/Oy-SPiRY08-885.jpeg 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Good Plan&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/Oy-SPiRY08-295.jpeg&quot; width=&quot;885&quot; height=&quot;283&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;What caused the plan to tip in the first place and when I don&#39;t know, as our ReportServer database didn&#39;t have one of the best features of 2016 enabled at the time (&lt;a href=&quot;https://docs.microsoft.com/en-us/sql/relational-databases/performance/monitoring-performance-by-using-the-query-store&quot;&gt;Query Store&lt;/a&gt;). That has now been rectified, and going forward the data about execution times/timeouts will now feed into our query store aggregation system and make spotting this kind of issue easy.&lt;/p&gt;
&lt;h2 id=&quot;bonus-missing-users&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/#bonus-missing-users&quot;&gt;Bonus: Missing users&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One additional issue we were facing is what happens when a Sid mapping isn&#39;t found. Rather than cache the fact there is no account returned for a given sid, nothing is cached and each request goes back to the DC. Some of our reports have been around for a long time and their their Sids are no longer in the directory - but SSRS still faithfully attempts to resolve the username of the report creator, sends the request on to the remote DC, and gets nothing back (the suser_sname function returns null).&lt;/p&gt;
&lt;p&gt;This is easy to test - assume you&#39;ve got two users (Tim and Tom). Tim still exists, but Tom doesn&#39;t. We&#39;re querying a remote DC for these users. To test yourself you&#39;ll need to get the Sid of a deleted user account - querying the Users table on an old ReportServer database might be the easiest way (the table also stores the account name, which is how SSRS is able to display information even if the Sid lookup returns null).&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@timSid&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;varbinary&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0x00&lt;/span&gt;…&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@tomSid&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;varbinary&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0x00&lt;/span&gt;…&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; suser_sname&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@timSid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;-- takes 90ms&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; suser_sname&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@timSid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;-- takes 0ms - cached&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; suser_sname&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@tomSid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;-- takes 90ms&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; suser_sname&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@tomSid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;-- takes 90ms - not cached&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Sid contains information about which domain the user belongs to - if you test with users removed from local domains you&#39;ll probably still be able to observe the result though the delta is much smaller (as LsaLookupSid is cached on the machine that makes the query - in this case our SQL Server).&lt;/p&gt;
&lt;p&gt;Based on this, my recommendation is that you ensure the owner and &#39;lasted updated by&#39; users of all reports are set to a user that currently exists, preferably in a local domain. Clearing old users out of the User table is something else you can do (so at least if the plan does tip you are minimising the impact).&lt;/p&gt;
</description>
      <pubDate>Sat, 21 Oct 2017 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2017/10/troubleshooting-an-ssrs-slowdown-portal-delays-preemptive_os_lookupaccountsid-and-bad-plans/</guid>
    </item>
    <item>
      <title>Caching replica state to eliminate HADR_CLUSAPI_CALL waits on dm_hadr_availability_replica_states</title>
      <link>https://tjaddison.com/blog/2017/10/caching-replica-state-to-eliminate-hadr_clusapi_call-waits-on-dm_hadr_availability_replica_states/</link>
      <description>&lt;p&gt;We use &lt;a href=&quot;https://github.com/taddison/tsqlScheduler&quot;&gt;tsqlscheduler&lt;/a&gt; to manage most of our SQL Jobs (a few hundred jobs in a several overlapping AGs), and when lots of schedules overlap (e.g. many concurrent jobs kick off on the hour) we saw waits and blocking on the function that was attempting to determine whether or not the server was the primary replica:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/10/caching-replica-state-to-eliminate-hadr_clusapi_call-waits-on-dm_hadr_availability_replica_states/dxzN7fnhLr-295.avif 295w, https://tjaddison.com/blog/2017/10/caching-replica-state-to-eliminate-hadr_clusapi_call-waits-on-dm_hadr_availability_replica_states/dxzN7fnhLr-590.avif 590w, https://tjaddison.com/blog/2017/10/caching-replica-state-to-eliminate-hadr_clusapi_call-waits-on-dm_hadr_availability_replica_states/dxzN7fnhLr-885.avif 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/10/caching-replica-state-to-eliminate-hadr_clusapi_call-waits-on-dm_hadr_availability_replica_states/dxzN7fnhLr-295.webp 295w, https://tjaddison.com/blog/2017/10/caching-replica-state-to-eliminate-hadr_clusapi_call-waits-on-dm_hadr_availability_replica_states/dxzN7fnhLr-590.webp 590w, https://tjaddison.com/blog/2017/10/caching-replica-state-to-eliminate-hadr_clusapi_call-waits-on-dm_hadr_availability_replica_states/dxzN7fnhLr-885.webp 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2017/10/caching-replica-state-to-eliminate-hadr_clusapi_call-waits-on-dm_hadr_availability_replica_states/dxzN7fnhLr-295.jpeg 295w, https://tjaddison.com/blog/2017/10/caching-replica-state-to-eliminate-hadr_clusapi_call-waits-on-dm_hadr_availability_replica_states/dxzN7fnhLr-590.jpeg 590w, https://tjaddison.com/blog/2017/10/caching-replica-state-to-eliminate-hadr_clusapi_call-waits-on-dm_hadr_availability_replica_states/dxzN7fnhLr-885.jpeg 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Blocking&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/10/caching-replica-state-to-eliminate-hadr_clusapi_call-waits-on-dm_hadr_availability_replica_states/dxzN7fnhLr-295.jpeg&quot; width=&quot;885&quot; height=&quot;351&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;This function queries the DMV &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-hadr-availability-replica-states-transact-sql&quot;&gt;sys.dm_hadr_availability_replica_states&lt;/a&gt;, and we found that this DMV doesn&#39;t perform so well when the cluster size, number of AGs, and number of concurrent queries against the DMV rises.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Querying for the replica&#39;s role&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt;      &lt;span class=&quot;token variable&quot;&gt;@role&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ars&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;role_desc&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt;        sys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dm_hadr_availability_replica_states ars&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;inner&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;join&lt;/span&gt;  sys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;availability_groups ag&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt;          ars&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;group_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ag&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;group_id&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt;       ag&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@availabilityGroupName&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt;         ars&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;is_local &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Under the hood this DMV is calling into the Windows clustering APIs to determine the current status of the replica. There isn&#39;t a tonne of information out there - some comments on this &lt;a href=&quot;https://dba.stackexchange.com/a/131636&quot;&gt;SO answer&lt;/a&gt; confirm what the DMV is doing, and &lt;a href=&quot;https://blogs.msdn.microsoft.com/sqlmeditation/2017/08/18/what-really-happens-when-hadr_clusapi_call-wait-type-is-set/&quot;&gt;this MSFT post&lt;/a&gt; adds a little more detail. After checking some of the obvious items listed in the second post I decided that maybe we shouldn&#39;t be querying the DMV quite so often, and given how rarely the information changes (&amp;quot;is my node the primary replica?&amp;quot;) caching this information seemed like a good fit.&lt;/p&gt;
&lt;h2 id=&quot;implementing-caching&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/10/caching-replica-state-to-eliminate-hadr_clusapi_call-waits-on-dm_hadr_availability_replica_states/#implementing-caching&quot;&gt;Implementing caching&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Caching of the replica state is available in any release of &lt;a href=&quot;https://github.com/taddison/tsqlScheduler&quot;&gt;tsqlscheduler&lt;/a&gt; starting with &lt;a href=&quot;https://github.com/taddison/tsqlScheduler/releases/tag/1.1&quot;&gt;1.1&lt;/a&gt;. Each task now specifies whether or not it uses the cached replica check or not - most tasks should use the cached replica check, with the exception of the task which keeps the cache updated.&lt;/p&gt;
&lt;p&gt;The caching is implemented by a task that persists all replica states for the given AG, and is queried by the GetCachedAvailabilityGroupRole function.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update replica status&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;alter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;procedure&lt;/span&gt; scheduler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UpdateReplicaStatus&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; nocount &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; xact_abort &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt;  &lt;span class=&quot;token variable&quot;&gt;@availabilityGroup&lt;/span&gt; nvarchar&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;128&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; 	&lt;span class=&quot;token variable&quot;&gt;@availabilityGroup&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ag&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AvailabilityGroup&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; 	scheduler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;GetAvailabilityGroup&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; ag&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;merge&lt;/span&gt; scheduler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ReplicaStatus &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; rs&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt;      ar&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;replica_server_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ars&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;role_desc&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt;        sys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dm_hadr_availability_replica_states ars&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;inner&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;join&lt;/span&gt;  sys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;availability_groups ag&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt;          ars&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;group_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ag&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;group_id&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;join&lt;/span&gt;        sys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;availability_replicas &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; ar&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt;          ar&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;replica_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ars&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;replica_id&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt;       ag&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@availabilityGroup&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; src&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt; src&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;replica_server_name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; rs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;HostName&lt;br /&gt;    &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt;	src&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;role_desc &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@availabilityGroup&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;matched&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;update&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; rs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AvailabilityGroupRole &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; src&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;role_desc&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;matched&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;insert&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; AvailabilityGroup&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; HostName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; AvailabilityGroupRole &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@availabilityGroup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; src&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;replica_server_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; src&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;role_desc &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;when&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;matched&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;by&lt;/span&gt; source&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Get cached role&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;create or alter function scheduler.GetCachedAvailabilityGroupRole
(
	@availabilityGroupName nvarchar(128)
)
returns nvarchar(60)
as
begin
	declare @role nvarchar(60);

	select 	@role = rs.AvailabilityGroupRole
	from 	scheduler.ReplicaStatus as rs
	where	rs.AvailabilityGroup = @availabilityGroupName
	and	rs.HostName = host_name();

	return coalesce(@role, N&#39;&#39;);
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Deploying these changes eliminated the blocking behaviour, and we&#39;ve seen the back of some fairly spectacular blocking chains we were able to produce when a server was under heavy load (yes, that is almost 3 minutes waiting to decide if the task can even run or not!).&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/10/caching-replica-state-to-eliminate-hadr_clusapi_call-waits-on-dm_hadr_availability_replica_states/_O7RCy0UQT-295.avif 295w, https://tjaddison.com/blog/2017/10/caching-replica-state-to-eliminate-hadr_clusapi_call-waits-on-dm_hadr_availability_replica_states/_O7RCy0UQT-590.avif 590w, https://tjaddison.com/blog/2017/10/caching-replica-state-to-eliminate-hadr_clusapi_call-waits-on-dm_hadr_availability_replica_states/_O7RCy0UQT-885.avif 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/10/caching-replica-state-to-eliminate-hadr_clusapi_call-waits-on-dm_hadr_availability_replica_states/_O7RCy0UQT-295.webp 295w, https://tjaddison.com/blog/2017/10/caching-replica-state-to-eliminate-hadr_clusapi_call-waits-on-dm_hadr_availability_replica_states/_O7RCy0UQT-590.webp 590w, https://tjaddison.com/blog/2017/10/caching-replica-state-to-eliminate-hadr_clusapi_call-waits-on-dm_hadr_availability_replica_states/_O7RCy0UQT-885.webp 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2017/10/caching-replica-state-to-eliminate-hadr_clusapi_call-waits-on-dm_hadr_availability_replica_states/_O7RCy0UQT-295.jpeg 295w, https://tjaddison.com/blog/2017/10/caching-replica-state-to-eliminate-hadr_clusapi_call-waits-on-dm_hadr_availability_replica_states/_O7RCy0UQT-590.jpeg 590w, https://tjaddison.com/blog/2017/10/caching-replica-state-to-eliminate-hadr_clusapi_call-waits-on-dm_hadr_availability_replica_states/_O7RCy0UQT-885.jpeg 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Much More Blocking&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/10/caching-replica-state-to-eliminate-hadr_clusapi_call-waits-on-dm_hadr_availability_replica_states/_O7RCy0UQT-295.jpeg&quot; width=&quot;885&quot; height=&quot;304&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
</description>
      <pubDate>Wed, 04 Oct 2017 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2017/10/caching-replica-state-to-eliminate-hadr_clusapi_call-waits-on-dm_hadr_availability_replica_states/</guid>
    </item>
    <item>
      <title>Creating recruitment funnel metrics in Power BI</title>
      <link>https://tjaddison.com/blog/2017/09/creating-recruitment-funnel-metrics-in-power-bi/</link>
      <description>&lt;p&gt;Creating a funnel chart in Power BI is extremely simple - drop your measure and group by column onto the canvas and format as a funnel chart. The tricky part is making sure your data is modelled correctly to give you the answers you need. If we assume that a simple 4-stage recruitment process (New Application - Phone Screen - Face to Face - Hire) has data captured in the following format:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/09/creating-recruitment-funnel-metrics-in-power-bi/RRH78kXbWT-291.avif 291w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/09/creating-recruitment-funnel-metrics-in-power-bi/RRH78kXbWT-291.webp 291w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Sample application data&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/09/creating-recruitment-funnel-metrics-in-power-bi/RRH78kXbWT-291.jpeg&quot; width=&quot;291&quot; height=&quot;221&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;When we create a funnel chart in Power BI we see the following:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/09/creating-recruitment-funnel-metrics-in-power-bi/9G52rN5vi6-295.avif 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/09/creating-recruitment-funnel-metrics-in-power-bi/9G52rN5vi6-295.webp 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Not the funnel we expected&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/09/creating-recruitment-funnel-metrics-in-power-bi/9G52rN5vi6-295.jpeg&quot; width=&quot;295&quot; height=&quot;268&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;That is technically correct (there are 3 applications in the New stage), but it isn&#39;t what we wanted (10 people applied and we hired 1 - which is not a 25% hire rate). While we could change our data (typically modifying the query to include multiple columns - one for each stage), this one is fairly easy to solve with a few DAX calculations to return the inclusive number of applications by stage.&lt;/p&gt;
&lt;p&gt;The core measure we build returns the number of applications which have got to &lt;em&gt;at least&lt;/em&gt; that stage. We do this by removing the filter context from both Stage and StageOrder (we typically visualise by Stage, but we rely on StageOrder for ranking stages), and then using the MAX function to pull the value of StageOrder from the filter context. In our case, the filter context will be the current group expression on the funnel.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Applications =
COUNTROWS( Applications )

Inclusive Applications =
CALCULATE (
    [Applications],
    FILTER (
        ALL ( Applications[Stage], Applications[StageOrder] ),
        Applications[StageOrder] &amp;gt;= MAX ( Applications[StageOrder] )
    )
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using this measure instead gives us the funnel we expected:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/09/creating-recruitment-funnel-metrics-in-power-bi/IhNWuM3c07-295.avif 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/09/creating-recruitment-funnel-metrics-in-power-bi/IhNWuM3c07-295.webp 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Funnel with inclusive applications&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/09/creating-recruitment-funnel-metrics-in-power-bi/IhNWuM3c07-295.jpeg&quot; width=&quot;295&quot; height=&quot;266&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;We can also define some additional DAX measures to report directly on the conversion rate between each step of the funnel. In the example below I&#39;ve only defined a subset of the measures - typically you&#39;d define these for all stages and conversion rates you cared about.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Hire =
CALCULATE (
    [Inclusive Applications], Applications[Stage] = &amp;quot;Hire&amp;quot;
)

Hire % All =
DIVIDE ( [Hire], [Applications], 0 )

Face to Face =
CALCULATE (
    [Inclusive Applications], Applications[Stage] = &amp;quot;Face to Face&amp;quot;
)

Hire % F2F =
DIVIDE ( [Hire], [Face to Face], 0 )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These can then be used when you want to compare those metrics between roles, locations, over time, or anything else you have in your model.&lt;/p&gt;
&lt;p&gt;If you&#39;re currently recruiting and you don&#39;t know what your funnel looks like I encourage you to read &lt;a href=&quot;http://randsinrepose.com/archives/how-to-recruit/&quot;&gt;how to recruit&lt;/a&gt; and then take another look at your funnel metrics (building them if needed).&lt;/p&gt;
&lt;p&gt;You can download an example Power BI desktop file with the sample data and formulae &lt;a href=&quot;https://tjaddison.com/blog/2017/09/creating-recruitment-funnel-metrics-in-power-bi/FunnelSample.pbix&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
</description>
      <pubDate>Sun, 10 Sep 2017 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2017/09/creating-recruitment-funnel-metrics-in-power-bi/</guid>
    </item>
    <item>
      <title>View currently executing tasks in tsqlScheduler</title>
      <link>https://tjaddison.com/blog/2017/09/view-currently-executing-tasks-in-tsqlscheduler/</link>
      <description>&lt;p&gt;&lt;a href=&quot;https://github.com/taddison/tsqlScheduler/releases/tag/1.0&quot;&gt;tsqlScheduler 1.0&lt;/a&gt; was released today, and now contains a feature which allows you to view currently running tasks by storing information about that task in &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/t-sql/functions/context-info-transact-sql&quot;&gt;context_info&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The view &lt;strong&gt;scheduler.CurrentlyExecutingTasks&lt;/strong&gt; can be queried and joined back to the core tables to produce results that tell us at a glance how long the task has been running for, and how long it took last time.&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt;	te&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StartDateTime&lt;br /&gt;		&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;datediff&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;second&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;te&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StartDateTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; getutcdate&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; DurationSeconds&lt;br /&gt;		&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Identifier&lt;br /&gt;		&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;lastResult&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StartDateTime &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; LastStartTime&lt;br /&gt;		&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;datediff&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;second&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;lastResult&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StartDateTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; lastResult&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;EndDateTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; LastDurationSeconds&lt;br /&gt;		&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;lastResult&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;IsError &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; LastIsError&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt;	scheduler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CurrentlyExecutingTasks &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; cet&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;join&lt;/span&gt;    scheduler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;GetInstanceId&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; id&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt;      cet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Instanceid &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Id&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;join&lt;/span&gt;	scheduler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Task &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; t&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt;		t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TaskId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TaskId&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;join&lt;/span&gt;	scheduler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TaskExecution &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; te&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt;		te&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ExecutionId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ExecutionId&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;outer&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;apply&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;top&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; scheduler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TaskExecution &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; teh&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; teh&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TaskId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TaskId&lt;br /&gt;	&lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; teh&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ExecutionId &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&gt;&lt;/span&gt; te&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ExecutionId&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;by&lt;/span&gt; ExecutionId &lt;span class=&quot;token keyword&quot;&gt;desc&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; lastResult&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/09/view-currently-executing-tasks-in-tsqlscheduler/Buqf5CdyE7-295.avif 295w, https://tjaddison.com/blog/2017/09/view-currently-executing-tasks-in-tsqlscheduler/Buqf5CdyE7-590.avif 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/09/view-currently-executing-tasks-in-tsqlscheduler/Buqf5CdyE7-295.webp 295w, https://tjaddison.com/blog/2017/09/view-currently-executing-tasks-in-tsqlscheduler/Buqf5CdyE7-590.webp 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2017/09/view-currently-executing-tasks-in-tsqlscheduler/Buqf5CdyE7-295.jpeg 295w, https://tjaddison.com/blog/2017/09/view-currently-executing-tasks-in-tsqlscheduler/Buqf5CdyE7-590.jpeg 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Currently executing tasks&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/09/view-currently-executing-tasks-in-tsqlscheduler/Buqf5CdyE7-295.jpeg&quot; width=&quot;590&quot; height=&quot;119&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;In this example there are four tasks which have been running for 55 seconds. They normally take 60 seconds to run, and they last ran a minute ago (with no errors).&lt;/p&gt;
&lt;p&gt;The rest of this post talks through how the mapping works.&lt;/p&gt;
&lt;h2 id=&quot;recap-of-tsqlscheduler&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/09/view-currently-executing-tasks-in-tsqlscheduler/#recap-of-tsqlscheduler&quot;&gt;Recap of tsqlScheduler&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;tsqlScheduler creates one SQL Agent job per entry in the &lt;em&gt;scheduler.Task&lt;/em&gt; table. The agent job calls the &lt;em&gt;scheduler.ExecuteTask&lt;/em&gt; procedure, which eventually calls sp_executesql to run the TSQL specified by the entry in the task table.&lt;/p&gt;
&lt;p&gt;The execution of of each task is logged in the &lt;em&gt;scheduler.TaskExecution&lt;/em&gt; table.&lt;/p&gt;
&lt;h2 id=&quot;storing-information-against-each-task&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/09/view-currently-executing-tasks-in-tsqlscheduler/#storing-information-against-each-task&quot;&gt;Storing information against each task&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In order to tag each execution with the metadata we need to tie back to the task &amp;amp; execution data we use context_info. This allows us to store up to 128 bytes of data against a session which can then be queried through the &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-exec-requests-transact-sql&quot;&gt;dm_exec_requests&lt;/a&gt; and &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-exec-sessions-transact-sql&quot;&gt;dm_exec_sessions&lt;/a&gt; DMVs.&lt;/p&gt;
&lt;p&gt;The information we&#39;ll store against each execution are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The scheduler instance identifier&lt;/li&gt;
&lt;li&gt;The task id&lt;/li&gt;
&lt;li&gt;The execution id&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each instance when deployed has a unique guid generated that identifies that instance (returned via the function &lt;em&gt;scheduler.GetInstanceId&lt;/em&gt;). We need this as the task id is a number, and so for any SQL instance with multiple scheduler instances deployed (e.g. one standalone and multiple AGs) we use the instance id to disambiguate which instance the task belongs to.&lt;/p&gt;
&lt;p&gt;Context info requires a binary payload, and rather than worrying about packing and unpacking a payload I&#39;ve opted to use json to allow for a fairly flexible schema (I&#39;d have preferred to use &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/t-sql/functions/session-context-transact-sql&quot;&gt;session_context&lt;/a&gt; but you can&#39;t query that from other sessions at the moment).&lt;/p&gt;
&lt;p&gt;As each task is executed the ExecuteTask procedure calls into the following procedure to build and store our json data:&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;alter&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;procedure&lt;/span&gt; scheduler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SetContextInfo&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;@instanceIdentifier&lt;/span&gt; uniqueidentifier&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@taskId&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@executionId&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@descriptor&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;128&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;br /&gt;	    &lt;span class=&quot;token string&quot;&gt;&#39;{ &quot;i&quot;:&quot;&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; cast&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@instanceIdentifier&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;36&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;	    &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&quot;,&quot;t&quot;:&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; cast&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@taskId&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;	    &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;,&quot;e&quot;:&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; cast&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@executionId&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;	    &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;}&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;declare&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@binaryPayload&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;varbinary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;128&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cast&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@descriptor&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;varbinary&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;128&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; context_info &lt;span class=&quot;token variable&quot;&gt;@binarypayload&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SQL Agent does use pooled connections, though it resets each connection before each job is executed, which means that we don&#39;t have to worry about resetting the context_info after every execution.&lt;/p&gt;
&lt;h2 id=&quot;viewing-task-information&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/09/view-currently-executing-tasks-in-tsqlscheduler/#viewing-task-information&quot;&gt;Viewing task information&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The view &lt;em&gt;scheduler.CurrentlyExecutingTasks&lt;/em&gt; returns one row for each task currently executing, regardless of the scheduler instance the task was deployed into.&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt;  tasks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;InstanceId&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;tasks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TaskId&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;tasks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ExecutionId&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt;    sys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dm_exec_requests &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; r&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;cross&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;apply&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; try_cast&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;context_info &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;128&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; ContextInfo&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; i&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;cross&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;apply&lt;/span&gt; openjson &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ContextInfo&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; N&lt;span class=&quot;token string&quot;&gt;&#39;$&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;		InstanceId uniqueidentifier	N&lt;span class=&quot;token string&quot;&gt;&#39;$.i&#39;&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;Taskid &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;	N&lt;span class=&quot;token string&quot;&gt;&#39;$.t&#39;&lt;/span&gt;&lt;br /&gt;		&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;ExecutionId &lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt; N&lt;span class=&quot;token string&quot;&gt;&#39;$.e&#39;&lt;/span&gt;&lt;br /&gt;	&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; tasks&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;context_info &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;x&lt;br /&gt;&lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt;   isjson&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ContextInfo&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Most sessions have no context_info associated with them (0x) so we can ignore those. For sessions which do have a payload we attempt to convert to varchar and then check if they&#39;re valid json. We then extract the json and correctly type the values.&lt;/p&gt;
&lt;p&gt;To filter the currently executing tasks for the current scheduler instance only (the database that contains the view), you can modify the query to join onto the function that returns the instance&#39;s unique Id:&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; cet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt;   scheduler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CurrentlyExecutingTasks &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; cet&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;join&lt;/span&gt;   scheduler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;GetInstanceId&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; id&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;on&lt;/span&gt;     cet&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Instanceid &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Id&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the same join that is used in the monitoring query at the start of the post, and is essential in deployments where multiple scheduler schemas are concurrently executing.&lt;/p&gt;
</description>
      <pubDate>Sun, 03 Sep 2017 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2017/09/view-currently-executing-tasks-in-tsqlscheduler/</guid>
    </item>
    <item>
      <title>Building an OMS metric alert to Slack bridge with Azure functions</title>
      <link>https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/</link>
      <description>&lt;p&gt;&lt;a href=&quot;https://tjaddison.com/blog/2017/08/migrating-function-app-scripts-to-a-class-library&quot;&gt;Previously&lt;/a&gt; we deployed a function app with three functions to send Slack notifications for OMS alerts based on CPU, memory, and disk. We&#39;ll now deploy a function with a single function designed to handle any OMS metric alert and send it to slack. As an added bonus we&#39;ll also support:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Overriding the thresholds to alert/critically alert based on server + metric&lt;/li&gt;
&lt;li&gt;Use @channel to notify for critical alerts&lt;/li&gt;
&lt;li&gt;Sending alerts to multiple channels based on either server or metric name&lt;/li&gt;
&lt;li&gt;Configuring defaults directly in the OMS alert, allowing rapid deployment of new alerts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/WrQHiPnUTM-295.avif 295w, https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/WrQHiPnUTM-590.avif 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/WrQHiPnUTM-295.webp 295w, https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/WrQHiPnUTM-590.webp 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/WrQHiPnUTM-295.jpeg 295w, https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/WrQHiPnUTM-590.jpeg 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Example Alerts&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/WrQHiPnUTM-295.jpeg&quot; width=&quot;590&quot; height=&quot;83&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;If you want to get started quickly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can grab a copy of the finished function app from &lt;a href=&quot;https://github.com/taddison/blog-oms-to-slack/tree/master/GenericMetricFunction&quot;&gt;this repo&lt;/a&gt; which you&#39;ll need to deploy as an Azure function (updating the Slack endpoint in SlackHelper.cs)&lt;/li&gt;
&lt;li&gt;Configure your alerts using the &lt;a href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/#sample-oms-alert-configs&quot;&gt;examples shown at the bottom&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Otherwise, read on for more details on what the function looks like, and more details on configuring the alerts and overrides.&lt;/p&gt;
&lt;h2 id=&quot;solution-overview&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/#solution-overview&quot;&gt;Solution Overview&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The solution is comprised of one or more alerts in OMS, an Azure function, and a Slack endpoint&lt;/li&gt;
&lt;li&gt;When an alert is raised in OMS it calls our Azure function via a webhook and passes the alert configuration along with the alert search results that raised the alert&lt;/li&gt;
&lt;li&gt;The Azure function uses the configuration and any deployed overrides to determine whether or not to alert, what channels to send the alert to, and whether or not to notify the channel (@channel)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;configuring-the-oms-alert&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/#configuring-the-oms-alert&quot;&gt;Configuring the OMS alert&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For every metric you want to alert on an OMS alert must be configured. While you can use almost any query you want to trigger an alert, there are a few things you&#39;ll have to do for each alert to get it to correctly route through the Azure function:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The alert must be a metric alert (trigger based on metric measurement rather than number of results)&lt;/li&gt;
&lt;li&gt;The search query must only group by one value in addition to TimeGenerated (typically ComputerName, though in some cases you might have a Computer + InstanceName, in which case you should concatenate them with a pipe (Computer|InstanceName))
&lt;ul&gt;
&lt;li&gt;If you group by more than one value the alert will not fire in OMS&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The alert must specify a webhook&lt;/li&gt;
&lt;li&gt;Has to specify a webhook action&lt;/li&gt;
&lt;li&gt;The webhook must contain a custom JSON payload that contains all the parameters expected by the Azure function (see &lt;a href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/#custom-json-payload&quot;&gt;Custom JSON payload&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;example-oms-alert&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/#example-oms-alert&quot;&gt;Example OMS Alert&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Note that this example contains defaults appropriate for my environment - you may wish to modify the frequency of checks, thresholds, and suppressions.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/Hd2jvV5a0A-295.avif 295w, https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/Hd2jvV5a0A-590.avif 590w, https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/Hd2jvV5a0A-885.avif 885w, https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/Hd2jvV5a0A-1180.avif 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/Hd2jvV5a0A-295.webp 295w, https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/Hd2jvV5a0A-590.webp 590w, https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/Hd2jvV5a0A-885.webp 885w, https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/Hd2jvV5a0A-1180.webp 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/Hd2jvV5a0A-295.jpeg 295w, https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/Hd2jvV5a0A-590.jpeg 590w, https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/Hd2jvV5a0A-885.jpeg 885w, https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/Hd2jvV5a0A-1180.jpeg 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Example Alert Config&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/Hd2jvV5a0A-295.jpeg&quot; width=&quot;1180&quot; height=&quot;587&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h3 id=&quot;search-query&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/#search-query&quot;&gt;Search Query&lt;/a&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Perf
| where CounterName == &amp;quot;Available MBytes&amp;quot;
| summarize AggregatedValue= avg(CounterValue) by bin(TimeGenerated, 1m), Computer
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;alert-settings&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/#alert-settings&quot;&gt;Alert Settings&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Time Window: 5 minutes&lt;/li&gt;
&lt;li&gt;Alert Frequency: 5 minutes&lt;/li&gt;
&lt;li&gt;Generate alert based on: Metric measurement&lt;/li&gt;
&lt;li&gt;Aggregate value: Less than 10,000&lt;/li&gt;
&lt;li&gt;Trigger alert based on: Total breaches greater than 3&lt;/li&gt;
&lt;li&gt;Suppress alerts for: 20 minutes&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;custom-json-payload&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/#custom-json-payload&quot;&gt;Custom JSON payload&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The custom JSON payload contains both generic information about the alert (how to format the numbers, what the alert is) as well as defaults for what constitutes a warning or critical alert.&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;IncludeSearchResults&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;WarningThreshold&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;CriticalThreshold&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;ValueMultiplier&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;Channel&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#oms-alerts&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;LessThanThresholdIsBad&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;AlertMessage&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Infra - Memory&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;MetricName&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Free Megabytes&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;FormatString&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;N0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;ObservationThreshold&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All parameters are mandatory (if you don&#39;t specify them the function will fail). Note that you cannot test the webhook by using the &#39;Test Webhook&#39; button as it will not include the search results.&lt;/p&gt;
&lt;h4 id=&quot;oms-parameters&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/#oms-parameters&quot;&gt;OMS Parameters&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;IncludeSearchResults&lt;/em&gt;: This must always be set to true so that the search query results will be included in the payload&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;alert-information&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/#alert-information&quot;&gt;Alert Information&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;LessThanThresholdIsBad&lt;/em&gt;: When true the aggregate value is compared against the warning/critical thresholds with a LessThan operator. When false GreaterThan is used.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;ValueMultiplier&lt;/em&gt;: The value to multiply each metric value by before comparing to thresholds of formatting. Typically 1.0 or 0.01 (most perfmon counters report 50% as 50 rather than 0.5, which when formatted as P0 would show 5000%)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;AlertMessage&lt;/em&gt;: The start of the message sent to the slack channel - see below for examples&lt;/li&gt;
&lt;li&gt;&lt;em&gt;FormatString&lt;/em&gt;: How the metric should be formatted, takes a &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings&quot;&gt;.Net format string&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;ObservationThreshold&lt;/em&gt;: At least this many observations must be above (below) the threshold to trigger the alert (or upgrade from warning to critical)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;MetricName&lt;/em&gt;: The name of the metric - this is included in the Slack notification&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;alert-defaults&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/#alert-defaults&quot;&gt;Alert Defaults&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;These can be overridden by the function based on the server/metric.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;WarningThreshold&lt;/em&gt;: The value which if the metric is above (below) the function app won&#39;t send a Slack notification&lt;/li&gt;
&lt;li&gt;&lt;em&gt;CriticalThreshold&lt;/em&gt;: The value which if the metric is above (below) the function app will mark that notification as critical&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Channel&lt;/em&gt;: The default channel for this alert. Channels can be added to but never removed from an alert.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;WarningThreshold&lt;/em&gt;, &lt;em&gt;LessThanThresholdIsBad&lt;/em&gt; will typically mirror the settings of the alert in OMS, while &lt;em&gt;ObservationThreshold&lt;/em&gt; is typically one more than its corresponding OMS value.&lt;/p&gt;
&lt;h4 id=&quot;sample-messages&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/#sample-messages&quot;&gt;Sample Messages&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Assuming Server1 was low on memory, the warning &amp;amp; critical alerts based on the configuration above might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Infra - Memory [WARN] :: Server1 :: 9,400/9,500/9,600 (min/avg/max Free Megabytes)

Infra - Memory [CRIT] :: Server1 :: 3,400/3,500/3,600 (min/avg/max Free Megabytes
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;sample-oms-alert-configs&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/#sample-oms-alert-configs&quot;&gt;Sample OMS Alert Configs&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;cpu&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/#cpu&quot;&gt;CPU&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Alerts when CPU exceeds a threshold.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Infra - CPU [CRIT] :: Server1 :: 56%/59%/61% (min/avg/max Processor Usage %)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;search-query-1&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/#search-query-1&quot;&gt;Search Query&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;Perf
| where CounterName == &amp;quot;% Processor Time&amp;quot;
| summarize AggregatedValue = avg(CounterValue) by bin(TimeGenerated, 1m), Computer
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;json&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/#json&quot;&gt;JSON&lt;/a&gt;&lt;/h4&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;IncludeSearchResults&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;WarningThreshold&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.35&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;CriticalThreshold&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.45&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;ValueMultiplier&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.01&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;Channel&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#oms-alerts&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;LessThanThresholdIsBad&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;AlertMessage&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Infra - CPU&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;MetricName&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Processor Usage %&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;FormatString&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;P0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;ObservationThreshold&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;memory&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/#memory&quot;&gt;Memory&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Alerts when free memory drops below a threshold. Measured in megabytes.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Infra - Memory [WARN] :: Server1 :: 9,400/9,500/9,600 (min/avg/max Free Megabytes)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;search-query-2&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/#search-query-2&quot;&gt;Search Query&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;Perf
| where CounterName == &amp;quot;Available MBytes&amp;quot;
| summarize AggregatedValue= avg(CounterValue) by bin(TimeGenerated, 1m),Computer
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;json-1&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/#json-1&quot;&gt;JSON&lt;/a&gt;&lt;/h4&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;IncludeSearchResults&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;WarningThreshold&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;CriticalThreshold&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;ValueMultiplier&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;Channel&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#oms-alerts&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;LessThanThresholdIsBad&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;AlertMessage&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Infra - Memory&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;MetricName&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Free Megabytes&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;FormatString&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;N0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;ObservationThreshold&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;disk&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/#disk&quot;&gt;Disk&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Alerts when a logical volume drops below a percentage threshold of free space. Note we group by a custom value (Computer + pipe + InstanceName).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Infra - Disk [WARN] :: Server1 - C: :: 5%/5%/5% (min/avg/max Free Space %)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;search-query-3&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/#search-query-3&quot;&gt;Search Query&lt;/a&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;Perf
| where ObjectName == &amp;quot;LogicalDisk&amp;quot;
| where CounterName == &amp;quot;% Free Space&amp;quot; and InstanceName != &amp;quot;_Total&amp;quot;
| extend ServerDrive = strcat(Computer,&amp;quot;|&amp;quot;,InstanceName)
| summarize AggregatedValue = min(CounterValue) by bin(TimeGenerated,5m), ServerDrive
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;json-2&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/#json-2&quot;&gt;JSON&lt;/a&gt;&lt;/h4&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;IncludeSearchResults&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;WarningThreshold&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;CriticalThreshold&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;ValueMultiplier&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.01&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;Channel&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#oms-alerts&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;LessThanThresholdIsBad&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;AlertMessage&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Infra - Disk&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;MetricName&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Free Space %&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;FormatString&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;P0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;ObservationThreshold&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;the-azure-function&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/#the-azure-function&quot;&gt;The Azure Function&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The flow of a webhook through the Azure function is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Webhook with alert configuration (custom JSON) and alert results (SearchResults) calls the OMSMetricToSlack function&lt;/li&gt;
&lt;li&gt;OMSMetricToSlack parses the data into an Alert object and then passes that to the AlertProcessor&lt;/li&gt;
&lt;li&gt;AlertProcessor determines the Computer name (we could be dealing with either a Computer or some combination of Computer + other value)&lt;/li&gt;
&lt;li&gt;AlertProcessor looks up any threshold overrides for the combination of Computer + Metric (could be higher/lower)&lt;/li&gt;
&lt;li&gt;AlertProcessor compares the metrics against the thresholds, and if ObservationThreshold number of values exceed threshold values the alert is set as warning or critical
&lt;ul&gt;
&lt;li&gt;If the alert is neither a warning or a critical nothing further happens&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;AlertProcessor builds the list of channels to send to, starting with the default channel from the alert config and adding any additional channels from computer or metric name overrides&lt;/li&gt;
&lt;li&gt;AlertProcessor builds a message and sends the message to every channel&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;omsmetrictoslack&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/#omsmetrictoslack&quot;&gt;OMSMetricToSlack&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;OMSMetricToSlack&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;FunctionName&lt;/span&gt;&lt;span class=&quot;token attribute-arguments&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;OMSMetricToSlack&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Task&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpTrigger&lt;/span&gt;&lt;span class=&quot;token attribute-arguments&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;WebHookType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;genericJson&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpRequestMessage&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TraceWriter&lt;/span&gt; log&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Content&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;ReadAsAsync&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;OMSPayload&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; metrics &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SearchResults&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Tables&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Rows&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;MetricValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;DateTime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Parse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ToString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Double&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Parse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ToString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ValueMultiplier&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// Server1|E:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; computerName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SearchResults&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Tables&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Rows&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ToString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; instanceName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;computerName&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Contains&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;&#39;|&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; split &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; computerName&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token char&quot;&gt;&#39;|&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;            computerName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; split&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;            instanceName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; split&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; alert &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;Alert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;            data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Channel&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;WarningThreshold&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CriticalThreshold&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;LessThanThresholdIsBad&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AlertMessage&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MetricName&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;FormatString&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; computerName&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; instanceName&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ObservationThreshold&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; metrics&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        AlertProcessor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ProcessAlert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;alert&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;CreateResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;HttpStatusCode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;OK&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;alertprocessor&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/#alertprocessor&quot;&gt;AlertProcessor&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The example alert processor class below contains a few overrides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For the database servers, we want to warn when CPU exceeds 30%, and critically alert when CPU exceeds 50%&lt;/li&gt;
&lt;li&gt;For the app servers, we want to warn when CPU exceeds 60%, and critically alert when CPU exceeds 85%&lt;/li&gt;
&lt;li&gt;We want to send all database server alerts to the #database channel in addition to whatever the default channel is&lt;/li&gt;
&lt;li&gt;We want to send all alerts based on SQL Batch Requests/second to the #database channel too&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that the Computer should match exactly the name of the computer in OMS (case sensitive). The name of the performance counter is &lt;em&gt;not&lt;/em&gt; the name of the performance counter in OMS, it is the value you&#39;ve given to the &lt;em&gt;MetricName&lt;/em&gt; property in the alert configuration. Take care to ensure you do not give duplicate metric names to different alerts.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AlertProcessor&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Func&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; LessThan &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt;&lt;/span&gt; threshold&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; threshold&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Func&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; MoreThan &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt;&lt;/span&gt; threshold&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; threshold&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ProcessAlert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Alert&lt;/span&gt; alert&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// Is this a &amp;lt; or &gt; alert?&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; comparison &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;alert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;LessThanThresholdIsBad &lt;span class=&quot;token punctuation&quot;&gt;?&lt;/span&gt;&lt;/span&gt; LessThan &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; MoreThan&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// Machine-specific overrides?&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;critical&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; warning&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;GetMachineDefaultThresholdOverrides&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;alert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MachineName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; alert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MetricName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; criticalThreshold &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; critical &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; alert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DefaultCriticalThreshold&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; warningThreshold &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; warning &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; alert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DefaultWarningThreshold&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// Where should the alert go&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token class-name&quot;&gt;IEnumerable&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; channels &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;List&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; alert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DefaultChannel &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        channels &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; channels&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Union&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;GetMachineChannels&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;alert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MachineName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        channels &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; channels&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Union&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;GetMetricChannels&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;alert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MetricName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// Aggregate metrics to produce a single summary record&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; totals &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; alert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MetricValues&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;GroupBy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;_ &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;g &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            Average &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; g&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Average&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;m &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; m&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;Min &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; g&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Min&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;m &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; m&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;Max &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; g&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Max&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;m &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; m&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;Critical &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; g&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;m &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;comparison&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;m&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; criticalThreshold&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;Warning &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; g&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;m &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;comparison&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;m&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; warningThreshold&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Single&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// Determine alert criticality&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; isWarning &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; totals&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Warning &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; alert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ObservationThreshold&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; isCritical &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; totals&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Critical &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; alert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ObservationThreshold&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// If the alert doesn&#39;t cross the warning threshold return&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;isWarning&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// Build message&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// Infra - CPU [CRIT] :: Server1 :: 56%/59%/61% (min/avg/max Processor Usage %)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// Infra - Disk [WARN] :: Server1 - E: :: 5%/6%/6% (min/avg/max Free Space %)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; instance &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;alert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;InstanceName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;$&quot; - &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;alert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;InstanceName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; message &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;$&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;alert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;DefaultAlertMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; [&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;isCritical &lt;span class=&quot;token punctuation&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;CRIT&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;WARN&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;] :: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;alert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MachineName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;instance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; :: &quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        message &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;$&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;totals&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Min&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ToString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;alert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;FormatString&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;totals&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Average&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ToString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;alert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;FormatString&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;totals&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Max&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ToString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;alert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;FormatString&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; &quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        message &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;$&quot;(min/avg/max &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;alert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;MetricName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;)&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;isCritical&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            message &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; @channel&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// Send message&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; channel &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; channels&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; SlackHelper&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;SendSlackMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;channel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;?&lt;/span&gt; warningThreshold&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;double&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;?&lt;/span&gt; criticalThreshold&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;GetMachineDefaultThresholdOverrides&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; machineName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; metricName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; combined &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;$&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;machineName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;metricName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;combined&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;DatabaseServer1|Processor Usage %&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;DatabaseServer2|Processor Usage %&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;DatabaseServer3|Processor Usage %&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;AppServer1|Processor Usage %&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;AppServer2|Processor Usage %&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.85&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;IEnumerable&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;GetMachineChannels&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; machineName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;machineName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;DatabaseServer1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;DatabaseServer2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;DatabaseServer3&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;List&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#database&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;List&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;IEnumerable&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;GetMetricChannels&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; metricName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;metricName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;SQL Batch Requests/sec&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;List&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#database&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;List&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;slackhelper&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/#slackhelper&quot;&gt;SlackHelper&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Ensure you replace the &lt;em&gt;slackUri&lt;/em&gt; with your own Slack endpoint.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SlackHelper&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Task&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;SendSlackMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; slackChannel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; slackUri &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://requestb.in/1hoo74a1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; slackUsername &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Alerts&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; client &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;HttpClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            System&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Diagnostics&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Debug&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WriteLine&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;$&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;slackChannel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; - &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;            &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; slackPayload &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;SlackMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Channel &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; slackChannel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Username &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; slackUsername&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; LinkNames &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; hook &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;StringContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;JsonConvert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;SerializeObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;slackPayload&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Encoding&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UTF8&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;PostAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;slackUri&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; hook&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;extending-the-solution&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/#extending-the-solution&quot;&gt;Extending the solution&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The solution is fairly robust and is fairly flexible in allowing you to alert on anything you can write a search query on. Some examples of things you might want to do with the function to help manage/debug/extend it are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add logging via calls to log.Info(...) or adding Application Insights to the function&lt;/li&gt;
&lt;li&gt;Create additional test payloads to ensure your overrides are working as expected&lt;/li&gt;
&lt;li&gt;Add some unit tests to verify the overrides&lt;/li&gt;
&lt;li&gt;Support case insensitive, wildcard, regex, etc., overrides for computer name&lt;/li&gt;
&lt;li&gt;Load configuration from a config file/Azure table storage&lt;/li&gt;
&lt;li&gt;Add additional notification methods, potentially using Azure function integrations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One of the easiest ways to get alerting wrong is to configure yourself into never receiving alerts, so I would suggest before you go too crazy with your config you come up with some way of testing what you&#39;re coding in.&lt;/p&gt;
&lt;p&gt;Happy alerting!&lt;/p&gt;
</description>
      <pubDate>Tue, 29 Aug 2017 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2017/08/building-an-oms-metric-alert-to-slack-bridge-with-azure-functions/</guid>
    </item>
    <item>
      <title>Migrating function app scripts to a class library</title>
      <link>https://tjaddison.com/blog/2017/08/migrating-function-app-scripts-to-a-class-library/</link>
      <description>&lt;p&gt;In the &lt;a href=&quot;https://tjaddison.com/blog/2017/08/monitoring-disk-cpu-and-memory-with-oms&quot;&gt;previous post&lt;/a&gt; we created a trio of function apps to send Slack notifications for OMS alerts based on CPU, Memory, and Disk. These were created using function app scripts, and the code was stored and tested directly in the Azure portal.&lt;/p&gt;
&lt;p&gt;In this post we&#39;ll walk through the steps required to replace those scripts with a class library that exposes three functions that can be hosted by the Azure function app runtime. It will do exactly the same thing, but crucially will allow us to develop &amp;amp; test locally.&lt;/p&gt;
&lt;p&gt;If you want to skp the interim steps and get straight to the finished code it is linked below. The code also contains the sample payloads, as well as a sample Pester test that validates the function completes without an error (returns a HTTP 200).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/taddison/blog-oms-to-slack/tree/138cd510adb2ceee5aaa272507d797a7aaf27b7c&quot;&gt;Start - three function apps as script files&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/taddison/blog-oms-to-slack/tree/master/ClassLibrary&quot;&gt;End - one class library exposing three functions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;migrating-the-function-scripts&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/migrating-function-app-scripts-to-a-class-library/#migrating-the-function-scripts&quot;&gt;Migrating the function scripts&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Create a new function app project (this step needs &lt;a href=&quot;https://www.visualstudio.com/downloads/&quot;&gt;Visual Studio 2017 15.3&lt;/a&gt;). If you don&#39;t see the option to create function apps you might not have installed the Azure development workload. The &lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-your-first-function-visual-studio&quot;&gt;official documentation&lt;/a&gt; walks through what is needed to build a function app with Visual Studio. &lt;a href=&quot;https://github.com/taddison/blog-oms-to-slack/tree/7b2955dcac59aa905583056b71bb379ce07d73de/ClassLibrary&quot;&gt;(repo)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now copy the script of the three functions into three separate files in the project folder. These are .csx (C# script) files in the portal, but we&#39;ll need to rename them to be .cs (C#) files. Right now the solution won&#39;t build, and we&#39;ll need to make a few changes to turn the C# scripts into valid C# files. &lt;a href=&quot;https://github.com/taddison/blog-oms-to-slack/tree/1fbb49e6ba41bbf982041a965b1b4cd96b6dd09c/ClassLibrary/OMSToSlack&quot;&gt;(repo)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To get the solution to build:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Remove the first line of each script (#r &amp;quot;Newtonsoft.Json&amp;quot;) - the Newtonsoft.Json package is already referenced by the Functions SDK, and references are managed at the project level rather than the file level&lt;/li&gt;
&lt;li&gt;Wrap the Run method and supporting classes in a class (one class per file, use the name of the file so that the file CPUToSlack.cs would contain &lt;strong&gt;public class CPUToSlack&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;Add usings for System.Threading.Tasks, System.Collections.Generic, and System.Linq, System.Net.Http, and Microsoft.Azure.WebJobs.Host&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The project will now build. &lt;a href=&quot;https://github.com/taddison/blog-oms-to-slack/tree/cc777ed70f349e02f06ba19b8a85af90b7bde63f/ClassLibrary/OMSToSlack&quot;&gt;(repo)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you run the project you&#39;ll see the function runtime start but an error message indicates that your app doesn&#39;t contain any functions.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/migrating-function-app-scripts-to-a-class-library/gjROvRMz_g-295.avif 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/migrating-function-app-scripts-to-a-class-library/gjROvRMz_g-295.webp 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;No Function Found&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/08/migrating-function-app-scripts-to-a-class-library/gjROvRMz_g-295.jpeg&quot; width=&quot;295&quot; height=&quot;194&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;To have our class library &#39;light up&#39; as a function we need to tell the functions runtime which methods should be treated as &#39;functions&#39;. We do this by specifying a FunctionName attribute on each method we wish to host as a function. We also need to provide some additional information about the input parameter (which for us is a webhook) so the function runtime knows what that function expects. Finally, we&#39;ll also need to add a using for Microsoft.Azure.WebJobs. &lt;a href=&quot;https://github.com/taddison/blog-oms-to-slack/tree/a3a884b31d060557abc17ea1d28539f177d0fd82/ClassLibrary/OMSToSlack&quot;&gt;(repo)&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MemoryToSlack&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;FunctionName&lt;/span&gt;&lt;span class=&quot;token attribute-arguments&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;MemoryToSlack&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Task&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token attribute&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpTrigger&lt;/span&gt;&lt;span class=&quot;token attribute-arguments&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;WebHookType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;genericJson&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpRequestMessage&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TraceWriter&lt;/span&gt; log&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running the function app again will show us that the runtime has discovered our functions and is now listening on a URL.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/migrating-function-app-scripts-to-a-class-library/BDuAMShEHZ-295.avif 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/migrating-function-app-scripts-to-a-class-library/BDuAMShEHZ-295.webp 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Function Found&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/08/migrating-function-app-scripts-to-a-class-library/BDuAMShEHZ-295.jpeg&quot; width=&quot;295&quot; height=&quot;194&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;To test the functions I found it easiest to use the json payloads we created earlier and post them to the function using Powershell. The below code is a Pester test that will post the relevant payload to each function, and return success only if the function returns a HTTP 200 (OK) response code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;OMSToSlack.tests.ps1&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$port&lt;/span&gt; = 7071&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$uriBase&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:&lt;span class=&quot;token variable&quot;&gt;$port&lt;/span&gt;/api&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$cpu&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-Content&lt;/span&gt; cpu-payload&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;json&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$memory&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-Content&lt;/span&gt; memory-payload&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;json&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$drive&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;Get-Content&lt;/span&gt; drive-payload&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;json&lt;br /&gt;&lt;br /&gt;Describe &lt;span class=&quot;token string&quot;&gt;&quot;OMS to Slack Function&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    It &lt;span class=&quot;token string&quot;&gt;&quot;Should return a 200 for a CPU payload&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Invoke-WebRequest&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Uri &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$uriBase&lt;/span&gt;/CPUToSlack&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Body &lt;span class=&quot;token variable&quot;&gt;$cpu&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Method Post &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ContentType &lt;span class=&quot;token string&quot;&gt;&quot;text/json&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StatusCode &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; Should Be 200&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    It &lt;span class=&quot;token string&quot;&gt;&quot;Should return a 200 for a Memory payload&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Invoke-WebRequest&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Uri &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$uriBase&lt;/span&gt;/MemoryToSlack&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Body &lt;span class=&quot;token variable&quot;&gt;$memory&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Method Post &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ContentType &lt;span class=&quot;token string&quot;&gt;&quot;text/json&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StatusCode &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; Should Be 200&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    It &lt;span class=&quot;token string&quot;&gt;&quot;Should return a 200 for a Drive payload&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Invoke-WebRequest&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Uri &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$uriBase&lt;/span&gt;/DriveToSlack &quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Body &lt;span class=&quot;token variable&quot;&gt;$drive&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Method Post &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ContentType &lt;span class=&quot;token string&quot;&gt;&quot;text/json&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;StatusCode &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; Should Be 200&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can run the test by running the &lt;strong&gt;Invoke-Pester&lt;/strong&gt; command in the folder that contains the file. A successful execution tells us that our functions are performing as we expect (or at least that they aren&#39;t throwing any exception!). As we make changes to the function code this test will allow us to verify that our functions still complete when we provide our known-good payloads. &lt;a href=&quot;https://github.com/taddison/blog-oms-to-slack/tree/442be35935326ab7c394175d9ccbea281dc133b1/ClassLibrary&quot;&gt;(repo)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/migrating-function-app-scripts-to-a-class-library/iWrhzJtWO6-295.avif 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/migrating-function-app-scripts-to-a-class-library/iWrhzJtWO6-295.webp 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Pester Test Success&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/08/migrating-function-app-scripts-to-a-class-library/iWrhzJtWO6-295.jpeg&quot; width=&quot;295&quot; height=&quot;81&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;The class library is now ready to be deployed and replace the script functions. To deploy and overwrite your functions you can download a publish profile from the Azure portal:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/migrating-function-app-scripts-to-a-class-library/JjRGUNUSv0-295.avif 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/migrating-function-app-scripts-to-a-class-library/JjRGUNUSv0-295.webp 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Download Publish Profile&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/08/migrating-function-app-scripts-to-a-class-library/JjRGUNUSv0-295.jpeg&quot; width=&quot;295&quot; height=&quot;53&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;And then when you publish the project in Visual Studio select &#39;Import profile&#39;, and then Publish. If you browse to the Azure portal you&#39;ll notice that you can no longer see your function code (the .csx files), and instead you see the function.json file that points to your class library and entry point:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/migrating-function-app-scripts-to-a-class-library/PKBRpX-y75-295.avif 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/migrating-function-app-scripts-to-a-class-library/PKBRpX-y75-295.webp 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Function JSON&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/08/migrating-function-app-scripts-to-a-class-library/PKBRpX-y75-295.jpeg&quot; width=&quot;295&quot; height=&quot;184&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;You can still test the function from the portal and watch the logs. You can also use the Pester test to run payloads against your live functions.&lt;/p&gt;
&lt;h2 id=&quot;next-steps&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/migrating-function-app-scripts-to-a-class-library/#next-steps&quot;&gt;Next steps&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As a final step I&#39;ve made a few changes to the repo so that the &lt;a href=&quot;https://github.com/taddison/blog-oms-to-slack/tree/18366b12d66a35b59f5f1aa0bf7175011a02adde/ClassLibrary&quot;&gt;current state&lt;/a&gt; no longer contains the duplicate class definitions in each of our functions (e.g. the OMSPayload class, the SlackMessage class).&lt;/p&gt;
&lt;p&gt;We&#39;ll be making several changes to the functions next time to extract common functionality, promote some values to config, and then finally start extending the functions to provide the features I&#39;ve talked about in previous posts.&lt;/p&gt;
</description>
      <pubDate>Wed, 23 Aug 2017 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2017/08/migrating-function-app-scripts-to-a-class-library/</guid>
    </item>
    <item>
      <title>Monitoring disk, CPU, and memory with OMS</title>
      <link>https://tjaddison.com/blog/2017/08/monitoring-disk-cpu-and-memory-with-oms/</link>
      <description>&lt;p&gt;In the &lt;a href=&quot;https://tjaddison.com/blog/2017/08/building-better-oms-alerts-with-function-apps&quot;&gt;previous post&lt;/a&gt; we built an OMS to Slack function app to alert on high CPU usage. We&#39;ll now extend that so we have alerts in place to cover us for the infrastructure basics - CPU, Memory and Drive Space.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/monitoring-disk-cpu-and-memory-with-oms/lLqlQ1BeEo-295.avif 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/monitoring-disk-cpu-and-memory-with-oms/lLqlQ1BeEo-295.webp 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Example Alerts&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/08/monitoring-disk-cpu-and-memory-with-oms/lLqlQ1BeEo-295.jpeg&quot; width=&quot;295&quot; height=&quot;66&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;The final code for the functions and example payloads are available in &lt;a href=&quot;https://github.com/taddison/blog-oms-to-slack/tree/master/MultipleFunctions&quot;&gt;this GitHub repo&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;oms-alerts&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/monitoring-disk-cpu-and-memory-with-oms/#oms-alerts&quot;&gt;OMS Alerts&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;These are designed to run over a 5 minute period, every 5 minutes, alerting on 3 consecutive breaches.&lt;/p&gt;
&lt;p&gt;As we&#39;re building metric alerts OMS expects us to produce a single metric grouped by a single value - as such the Disk Space alert is grouping based on the server name concatenated with the drive letter. Grouping by ComputerName, InstanceName (which is what we&#39;re really interested in) will prevent the alert from firing altogether.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CPU Usage (%)&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Perf
| where CounterName == &amp;quot;% Processor Time&amp;quot;
| summarize AggregatedValue = avg(CounterValue) by bin(TimeGenerated, 1m), Computer
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Disk Space Available (%)&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Perf
| where ObjectName == &amp;quot;LogicalDisk&amp;quot;
| where CounterName == &amp;quot;% Free Space&amp;quot; and InstanceName != &amp;quot;_Total&amp;quot;
| extend ServerDrive = strcat(Computer,&amp;quot;-&amp;quot;,InstanceName)
| summarize AggregatedValue = min(CounterValue) by bin(TimeGenerated, 1m), ServerDrive
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Memory Available (MB)&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Perf
| where CounterName == &amp;quot;Available MBytes&amp;quot;
| summarize AggregatedValue= avg(CounterValue) by bin(TimeGenerated, 1m), Computer
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;functions&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/monitoring-disk-cpu-and-memory-with-oms/#functions&quot;&gt;Functions&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To keep things simple I&#39;ve built one function app per-alert, all following the basic pattern from the CPU alert built in the &lt;a href=&quot;https://tjaddison.com/blog/2017/08/building-better-oms-alerts-with-function-apps&quot;&gt;previous post&lt;/a&gt;. The table below summarises the alerts and links to the example payload generated by the OMS alert, as well as the text of the function.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Alert&lt;/th&gt;
&lt;th&gt;Payload&lt;/th&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Measure&lt;/th&gt;
&lt;th&gt;Warning&lt;/th&gt;
&lt;th&gt;Critical&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CPU&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://tjaddison.com/blog/2017/08/monitoring-disk-cpu-and-memory-with-oms/cpu-payload.json&quot;&gt;cpu-payload.json&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://tjaddison.com/blog/2017/08/monitoring-disk-cpu-and-memory-with-oms/CPUToSlack.csx&quot;&gt;CPUToSlack.csx&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;CPU Used %&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://tjaddison.com/blog/2017/08/monitoring-disk-cpu-and-memory-with-oms/memory-payload.json&quot;&gt;memory-payload.json&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://tjaddison.com/blog/2017/08/monitoring-disk-cpu-and-memory-with-oms/MemoryToSlack.csx&quot;&gt;MemoryToSlack.csx&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Free Memory MB&lt;/td&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;5,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disk Space&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://tjaddison.com/blog/2017/08/monitoring-disk-cpu-and-memory-with-oms/drive-payload.json&quot;&gt;drive-payload.json&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://tjaddison.com/blog/2017/08/monitoring-disk-cpu-and-memory-with-oms/DriveToSlack.csx&quot;&gt;DriveToSlack.csx&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Free Space %&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/taddison/blog-oms-to-slack/tree/master/MultipleFunctions&quot;&gt;View all the above in a repo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you choose to modify the thresholds then you&#39;ll need to ensure you modify them both in the OMS alert definition (under the &#39;Metric Measurement&#39; section), as well as in the custom payload that specifies the warning/critical thresholds (under the &#39;Webhook&#39; section).&lt;/p&gt;
&lt;h2 id=&quot;next-steps&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/monitoring-disk-cpu-and-memory-with-oms/#next-steps&quot;&gt;Next steps&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now you&#39;ve got monitoring in place for CPU, Memory and Disk for your whole estate you&#39;re probably interested in the fan-out/decoupling of trigger/response I mentioned in the previous post. We&#39;ll get there soon, though in the next post we&#39;ll look at migrating the script functions into a class library, and reducing a bit of duplication.&lt;/p&gt;
</description>
      <pubDate>Mon, 21 Aug 2017 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2017/08/monitoring-disk-cpu-and-memory-with-oms/</guid>
    </item>
    <item>
      <title>Analysing blob storage metrics with Power BI</title>
      <link>https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/</link>
      <description>&lt;p&gt;Every new storage account has &lt;a href=&quot;https://docs.microsoft.com/en-us/rest/api/storageservices/about-storage-analytics-metrics&quot;&gt;Storage Analytics&lt;/a&gt; enabled by default, which captures both logs and metrics relating to that storage account. The metric data is logged into a table in the storage account, which is used to power the metrics you see in the portal but can also be downloaded and used programmatically, by a tool like storage explorer, or with Power BI.&lt;/p&gt;
&lt;p&gt;I recently found myself investigating why the cost of a resource group was so high (relative to expectations), and the driver turned out to be blob storage. As this application was supposed to be storing less than 1MB of data that was a surprise, and it was an even bigger surprise when the driver turned out to be the request charge.&lt;/p&gt;
&lt;p&gt;In order to drill a level deeper than the billing API (which only reports requests/day), I built a Power BI template to understand what was happening to the storage account. This template can be used to analyse any storage account, and reports on capacity, requests by operation type, ingress and egress, as well as estimating costs.&lt;/p&gt;
&lt;p&gt;You can download a copy of the template from &lt;a href=&quot;https://github.com/taddison/blog-power-bi-azure-storage&quot;&gt;this repo&lt;/a&gt;, or read on for more details about how to configure and use the report.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/LVmsFGgPE7-295.avif 295w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/LVmsFGgPE7-590.avif 590w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/LVmsFGgPE7-885.avif 885w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/LVmsFGgPE7-1180.avif 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/LVmsFGgPE7-295.webp 295w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/LVmsFGgPE7-590.webp 590w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/LVmsFGgPE7-885.webp 885w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/LVmsFGgPE7-1180.webp 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/LVmsFGgPE7-295.jpeg 295w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/LVmsFGgPE7-590.jpeg 590w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/LVmsFGgPE7-885.jpeg 885w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/LVmsFGgPE7-1180.jpeg 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Power BI Report&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/LVmsFGgPE7-295.jpeg&quot; width=&quot;1180&quot; height=&quot;677&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h1 id=&quot;using-the-power-bi-template&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/#using-the-power-bi-template&quot;&gt;Using the Power BI template&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;To use the Power BI template you&#39;ll need &lt;a href=&quot;https://powerbi.microsoft.com/en-us/desktop/&quot;&gt;Power BI Desktop&lt;/a&gt;, a copy of the template (latest version from &lt;a href=&quot;https://github.com/taddison/blog-power-bi-azure-storage&quot;&gt;this repo&lt;/a&gt;), and the storage account name and key.&lt;/p&gt;
&lt;p&gt;When you open the template it will ask you to provide parameter values for the table endpoint. This typically takes the form https://storageaccountname.table.core.windows.net/ and can be found on the &lt;em&gt;Properties&lt;/em&gt; blade of the storage account in the Azure portal. Note that even blob storage accounts have a table endpoint (the system uses table storage for all metrics, regardless of the kind of account).&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/TCydRGeMSb-295.avif 295w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/TCydRGeMSb-590.avif 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/TCydRGeMSb-295.webp 295w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/TCydRGeMSb-590.webp 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/TCydRGeMSb-295.jpeg 295w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/TCydRGeMSb-590.jpeg 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;TableEndpointURL&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/TCydRGeMSb-295.jpeg&quot; width=&quot;590&quot; height=&quot;185&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Once you&#39;ve provided the endpoint the template will connect and if this is the first time you&#39;ve connected it will ask you for the account key. The account key can be found on the &lt;em&gt;Access Keys&lt;/em&gt; blade of the storage account in the Azure portal.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/ZmxWr4kdQo-295.avif 295w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/ZmxWr4kdQo-590.avif 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/ZmxWr4kdQo-295.webp 295w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/ZmxWr4kdQo-590.webp 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/ZmxWr4kdQo-295.jpeg 295w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/ZmxWr4kdQo-590.jpeg 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;AccountKey&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/ZmxWr4kdQo-295.jpeg&quot; width=&quot;590&quot; height=&quot;215&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;The template will then download the capacity and metric data from the account. If you now save the Power BI file it will be saved as a report (.pbix), and next time you open this report you won&#39;t have to supply the details. In this way, you can quickly create reports for your key storage accounts that you can open and refresh as needed.&lt;/p&gt;
&lt;h2 id=&quot;date-range&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/#date-range&quot;&gt;Date range&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The report will pull all available capacity and metric data from the storage account. By default this is seven days - you may want to consider increasing this on your storage accounts as the cost is negligible if you are only storing metrics rather than logs - read more on &lt;a href=&quot;https://docs.microsoft.com/en-us/rest/api/storageservices/enabling-storage-metrics-and-viewing-metrics-data#what-charges-do-you-incur-when-you-enable-storage-metrics&quot;&gt;storage metrics cost&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Both pages of the report are filtered to the last seven days by default - you can modify the date range using the filter at the top right of each page. If you want to perform more granular filtering (e.g. for a given period of time) you will need to filter using the DateTime column from the appropriate table (Blob Capacity or Blob Metrics).&lt;/p&gt;
&lt;p&gt;Capacity information is captured once per-day, and the metric data we&#39;re using is captured once per-hour. More information is available about the tables we&#39;re reading from in the &lt;a href=&quot;https://docs.microsoft.com/en-us/rest/api/storageservices/storage-analytics-metrics-table-schema&quot;&gt;Metrics Table Schema documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;capacity-and-metrics-page&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/#capacity-and-metrics-page&quot;&gt;Capacity &amp;amp; Metrics page&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The first page of the report shows an overview of capacity, requests by type, and ingress/egress over time. By default, the requests and egress data is shown by the hour. If your storage account is only infrequently used (e.g. once per day) then graphs that are rendered by the hour may look odd. Hovering over any graph that is per-hour and clicking the &lt;em&gt;Drill Up&lt;/em&gt; button in the top left will change the graph from per-hour to per-day. The image below shows the same graph before and after drilling up.&lt;/p&gt;
&lt;p&gt;This happens because storage analytics will only create an entry in the metrics table if something happens - if your account has no activity it won&#39;t create an entry and our metric is then not contiguous (you&#39;ll have a record at midnight and then nothing until the next day at midnight). There are workarounds for this (use a different visualisation, render the missing data as 0, create a custom date/time axis, etc.) but drilling up typically works well.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/BJ5c5h66u1-295.avif 295w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/BJ5c5h66u1-590.avif 590w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/BJ5c5h66u1-885.avif 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/BJ5c5h66u1-295.webp 295w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/BJ5c5h66u1-590.webp 590w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/BJ5c5h66u1-885.webp 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/BJ5c5h66u1-295.jpeg 295w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/BJ5c5h66u1-590.jpeg 590w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/BJ5c5h66u1-885.jpeg 885w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Requests By Hour or Day&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/BJ5c5h66u1-295.jpeg&quot; width=&quot;885&quot; height=&quot;226&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h2 id=&quot;estimated-costs-page&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/#estimated-costs-page&quot;&gt;Estimated Costs page&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The second page of the report allows you to quickly check what the storage account should be costing (either per-month in the case of capacity, or per-hour/day for requests). Note that this is an exploration tool only - your bill is the only source of truth for the actual cost!&lt;/p&gt;
&lt;p&gt;The estimation page makes use of &#39;What-If&#39; parameters. If you slide the request unit cost or storage cost counters the graphs will update to show the expected price. This is useful when evaluating the potential cost impact of switching storage types (e.g. LRS to GRS).&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/0cFHI5nLzv-295.avif 295w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/0cFHI5nLzv-590.avif 590w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/0cFHI5nLzv-885.avif 885w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/0cFHI5nLzv-1180.avif 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/0cFHI5nLzv-295.webp 295w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/0cFHI5nLzv-590.webp 590w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/0cFHI5nLzv-885.webp 885w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/0cFHI5nLzv-1180.webp 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/0cFHI5nLzv-295.jpeg 295w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/0cFHI5nLzv-590.jpeg 590w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/0cFHI5nLzv-885.jpeg 885w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/0cFHI5nLzv-1180.jpeg 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Cost Estimation&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/0cFHI5nLzv-295.jpeg&quot; width=&quot;1180&quot; height=&quot;671&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;h1 id=&quot;building-the-report&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/#building-the-report&quot;&gt;Building the report&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Below are some details on how the report was built and how you can reconstruct some/all of the report.&lt;/p&gt;
&lt;h2 id=&quot;getting-the-data&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/#getting-the-data&quot;&gt;Getting the data&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Connecting to Table Storage (Get Data -&amp;gt; More -&amp;gt; Azure -&amp;gt; Azure Table Storage)&lt;/li&gt;
&lt;li&gt;Selecting an existing table (the storage analytics tables are hidden - if you don&#39;t have any existing tables you&#39;ll need to create one)&lt;/li&gt;
&lt;li&gt;Edit the query and then go to the advanced editor and replace the table name you selected with $MetricsHourPrimaryTransactionsBlob&lt;/li&gt;
&lt;li&gt;Name this query Blob Metrics&lt;/li&gt;
&lt;li&gt;Repeat the above but for the table $MetricsCapacityBlob and name the query Blob Capacity&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;filtering-out-summary-rows&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/#filtering-out-summary-rows&quot;&gt;Filtering out summary rows&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You&#39;ll now have both datasets available. One thing to note is that the transaction metrics contains one row for every API call as well as a summary record (user;All and system;all). If you include these in any table/graph you&#39;ll be overstating your usage, so I suggest you either filter them out of the import, or you create a field which allows you to filter the summary rows out at the report level. To do this I split the RowKey column by delimiter (;) into two columns - Access Type (system/user) and Transaction Type (API called), and then created a custom column with the following DAX expression:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Transaction Type Group = IF([Transaction Type] = &amp;quot;all&amp;quot;, &amp;quot;Summary&amp;quot;, &amp;quot;Detail&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Applying a report filter to limit all rows to Detail then prevents any double-counting.&lt;/p&gt;
&lt;h2 id=&quot;date-and-time&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/#date-and-time&quot;&gt;Date and time&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To make working with dates easier (including correlating the capacity &amp;amp; transactions tables on a common date) the following steps should be taken when importing data:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Format the partition key as a date/time, and rename to DateTime&lt;/li&gt;
&lt;li&gt;Create a copy of the formatted partition key column, format as a date, and rename to Date&lt;/li&gt;
&lt;li&gt;Complete these steps for both imported tables&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once the data is loaded you&#39;ll want to create a custom calendar table with the DAX expression:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Date = CALENDARAUTO()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then use the modelling view to create relationships between the capacity and metrics tables to the date table. You should then use this date on all date axes.&lt;/p&gt;
&lt;h2 id=&quot;expressions-and-formatting&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/#expressions-and-formatting&quot;&gt;Expressions and formatting&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;By default, all the capacity and transaction metric data comes in as strings. During the import process, you will want to format these as whole numbers. Once imported creating and formatting measures will ensure that people are actually able to read your report (I don&#39;t know about you, but total egress of 3565504437 doesn&#39;t mean as much to me as 3.5GB). Some example measures I used:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Capacity B = SUM([Capacity])
Capacity MB = [Capacity B]/1024/1024
Capacity GB = [Capacity MB]/1024
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For the costing data, my measures were all formatted as currency (USD) - you&#39;ll want to pick whatever currency your subscription is in, or use a decimal and ignore that.&lt;/p&gt;
&lt;h2 id=&quot;what-if&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/#what-if&quot;&gt;What-if&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To build the what-if functionality (e.g. cost per GB stored) you&#39;ll need to first create a what-if parameter. At the time of writing monthly &lt;a href=&quot;https://azure.microsoft.com/en-us/pricing/details/storage/blobs-general/&quot;&gt;blob storage costs&lt;/a&gt; range from $0.0224 to $0.061 per GB. To allow report users to explore this range we&#39;ll create a parameter that lets us go from 0.02 to 0.07 in 0.0001 increments (Modelling -&amp;gt; What If -&amp;gt; New Parameter):&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/SP9eB9SqhJ-295.avif 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/SP9eB9SqhJ-295.webp 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;What If&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/SP9eB9SqhJ-295.jpeg&quot; width=&quot;295&quot; height=&quot;283&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;Creating this will create a new table that has the series as well as the measure that returns the current value (based on any filters/slicers/calculation context). Note that the window is a shortcut to creating the table/expressions manually. Once we have the measure to compute cost we can then go on to create a measure to calculate the estimated monthly storage cost.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Storage Cost = GENERATESERIES(0.02, 0.07, 0.0001)
Storage Cost Value = SELECTEDVALUE(&#39;Storage Cost&#39;[Storage Cost], 0.024)
Monthly Storage Cost = [Capacity GB]*[Storage Cost Value]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;parameterising-the-table-endpoint&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/#parameterising-the-table-endpoint&quot;&gt;Parameterising the table endpoint&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To parameterise the table endpoint a new parameter needs to be created in Edit Queries. Once created you can then edit the source step (double click the first step of each table import) and you&#39;ll be able to select from value or parameter:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/XgKqAPl-c_-295.avif 295w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/XgKqAPl-c_-590.avif 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/XgKqAPl-c_-295.webp 295w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/XgKqAPl-c_-590.webp 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/XgKqAPl-c_-295.jpeg 295w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/XgKqAPl-c_-590.jpeg 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Create Parameter&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/XgKqAPl-c_-295.jpeg&quot; width=&quot;590&quot; height=&quot;183&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;You can manage the parameter value from the edit queries page (it will appear alongside tables and functions). When you export the report as a template it will then prompt for all parameter values when the template is first opened.&lt;/p&gt;
&lt;h2 id=&quot;browsing-azure-storage&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/#browsing-azure-storage&quot;&gt;Browsing Azure Storage&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For exploring Azure storage I highly recommend &lt;a href=&quot;http://storageexplorer.com/&quot;&gt;Storage Explorer&lt;/a&gt;, which will also show hidden tables and containers. The screenshot below shows an example of the $MetricsHourPrimaryTransactionsBlob table viewed with storage explorer.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/jFG965YI6F-295.avif 295w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/jFG965YI6F-590.avif 590w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/jFG965YI6F-885.avif 885w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/jFG965YI6F-1180.avif 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/jFG965YI6F-295.webp 295w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/jFG965YI6F-590.webp 590w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/jFG965YI6F-885.webp 885w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/jFG965YI6F-1180.webp 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/jFG965YI6F-295.jpeg 295w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/jFG965YI6F-590.jpeg 590w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/jFG965YI6F-885.jpeg 885w, https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/jFG965YI6F-1180.jpeg 1180w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Storage Explorer&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/jFG965YI6F-295.jpeg&quot; width=&quot;1180&quot; height=&quot;423&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;The full text of the M queries in the template is included below. They both expect a parameter called TableEndpointURL.&lt;/p&gt;
&lt;h2 id=&quot;m-queries&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/#m-queries&quot;&gt;M Queries&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Blob Metrics&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let
    Source = AzureStorage.Tables(TableEndpointURL),
    BlobMetrics = Source{[Name=&amp;quot;$MetricsHourPrimaryTransactionsBlob&amp;quot;]}[Data],
    ExpandedBlobMetrics = Table.ExpandRecordColumn(BlobMetrics, &amp;quot;Content&amp;quot;, {&amp;quot;TotalRequests&amp;quot;, &amp;quot;TotalBillableRequests&amp;quot;, &amp;quot;TotalIngress&amp;quot;, &amp;quot;TotalEgress&amp;quot;, &amp;quot;Availability&amp;quot;, &amp;quot;AverageE2ELatency&amp;quot;, &amp;quot;AverageServerLatency&amp;quot;, &amp;quot;PercentSuccess&amp;quot;, &amp;quot;PercentThrottlingError&amp;quot;, &amp;quot;PercentTimeoutError&amp;quot;, &amp;quot;PercentServerOtherError&amp;quot;, &amp;quot;PercentClientOtherError&amp;quot;, &amp;quot;PercentAuthorizationError&amp;quot;, &amp;quot;PercentNetworkError&amp;quot;, &amp;quot;Success&amp;quot;, &amp;quot;AnonymousSuccess&amp;quot;, &amp;quot;SASSuccess&amp;quot;, &amp;quot;ThrottlingError&amp;quot;, &amp;quot;AnonymousThrottlingError&amp;quot;, &amp;quot;SASThrottlingError&amp;quot;, &amp;quot;ClientTimeoutError&amp;quot;, &amp;quot;AnonymousClientTimeoutError&amp;quot;, &amp;quot;SASClientTimeoutError&amp;quot;, &amp;quot;ServerTimeoutError&amp;quot;, &amp;quot;AnonymousServerTimeoutError&amp;quot;, &amp;quot;SASServerTimeoutError&amp;quot;, &amp;quot;ClientOtherError&amp;quot;, &amp;quot;AnonymousClientOtherError&amp;quot;, &amp;quot;SASClientOtherError&amp;quot;, &amp;quot;ServerOtherError&amp;quot;, &amp;quot;AnonymousServerOtherError&amp;quot;, &amp;quot;SASServerOtherError&amp;quot;, &amp;quot;AuthorizationError&amp;quot;, &amp;quot;AnonymousAuthorizationError&amp;quot;, &amp;quot;SASAuthorizationError&amp;quot;, &amp;quot;NetworkError&amp;quot;, &amp;quot;AnonymousNetworkError&amp;quot;, &amp;quot;SASNetworkError&amp;quot;}, {&amp;quot;TotalRequests&amp;quot;, &amp;quot;TotalBillableRequests&amp;quot;, &amp;quot;TotalIngress&amp;quot;, &amp;quot;TotalEgress&amp;quot;, &amp;quot;Availability&amp;quot;, &amp;quot;AverageE2ELatency&amp;quot;, &amp;quot;AverageServerLatency&amp;quot;, &amp;quot;PercentSuccess&amp;quot;, &amp;quot;PercentThrottlingError&amp;quot;, &amp;quot;PercentTimeoutError&amp;quot;, &amp;quot;PercentServerOtherError&amp;quot;, &amp;quot;PercentClientOtherError&amp;quot;, &amp;quot;PercentAuthorizationError&amp;quot;, &amp;quot;PercentNetworkError&amp;quot;, &amp;quot;Success&amp;quot;, &amp;quot;AnonymousSuccess&amp;quot;, &amp;quot;SASSuccess&amp;quot;, &amp;quot;ThrottlingError&amp;quot;, &amp;quot;AnonymousThrottlingError&amp;quot;, &amp;quot;SASThrottlingError&amp;quot;, &amp;quot;ClientTimeoutError&amp;quot;, &amp;quot;AnonymousClientTimeoutError&amp;quot;, &amp;quot;SASClientTimeoutError&amp;quot;, &amp;quot;ServerTimeoutError&amp;quot;, &amp;quot;AnonymousServerTimeoutError&amp;quot;, &amp;quot;SASServerTimeoutError&amp;quot;, &amp;quot;ClientOtherError&amp;quot;, &amp;quot;AnonymousClientOtherError&amp;quot;, &amp;quot;SASClientOtherError&amp;quot;, &amp;quot;ServerOtherError&amp;quot;, &amp;quot;AnonymousServerOtherError&amp;quot;, &amp;quot;SASServerOtherError&amp;quot;, &amp;quot;AuthorizationError&amp;quot;, &amp;quot;AnonymousAuthorizationError&amp;quot;, &amp;quot;SASAuthorizationError&amp;quot;, &amp;quot;NetworkError&amp;quot;, &amp;quot;AnonymousNetworkError&amp;quot;, &amp;quot;SASNetworkError&amp;quot;}),
    ChangeInitialTypes = Table.TransformColumnTypes(ExpandedBlobMetrics,{ {&amp;quot;PartitionKey&amp;quot;, type datetime}, {&amp;quot;TotalRequests&amp;quot;, Int64.Type}, {&amp;quot;TotalBillableRequests&amp;quot;, Int64.Type}, {&amp;quot;TotalIngress&amp;quot;, Int64.Type}, {&amp;quot;TotalEgress&amp;quot;, Int64.Type}, {&amp;quot;Availability&amp;quot;, Int64.Type}, {&amp;quot;AverageE2ELatency&amp;quot;, Int64.Type}, {&amp;quot;AverageServerLatency&amp;quot;, Int64.Type}, {&amp;quot;PercentSuccess&amp;quot;, Int64.Type}, {&amp;quot;PercentThrottlingError&amp;quot;, Int64.Type}, {&amp;quot;PercentTimeoutError&amp;quot;, Int64.Type}, {&amp;quot;PercentServerOtherError&amp;quot;, Int64.Type}, {&amp;quot;PercentClientOtherError&amp;quot;, Int64.Type}, {&amp;quot;PercentAuthorizationError&amp;quot;, Int64.Type}, {&amp;quot;PercentNetworkError&amp;quot;, Int64.Type}, {&amp;quot;Success&amp;quot;, Int64.Type}, {&amp;quot;AnonymousSuccess&amp;quot;, Int64.Type}, {&amp;quot;SASSuccess&amp;quot;, Int64.Type}, {&amp;quot;ThrottlingError&amp;quot;, Int64.Type}, {&amp;quot;AnonymousThrottlingError&amp;quot;, Int64.Type}, {&amp;quot;SASThrottlingError&amp;quot;, Int64.Type}, {&amp;quot;ClientTimeoutError&amp;quot;, Int64.Type}, {&amp;quot;AnonymousClientTimeoutError&amp;quot;, Int64.Type}, {&amp;quot;SASClientTimeoutError&amp;quot;, Int64.Type}, {&amp;quot;ServerTimeoutError&amp;quot;, Int64.Type}, {&amp;quot;AnonymousServerTimeoutError&amp;quot;, Int64.Type}, {&amp;quot;SASServerTimeoutError&amp;quot;, Int64.Type}, {&amp;quot;ClientOtherError&amp;quot;, Int64.Type}, {&amp;quot;AnonymousClientOtherError&amp;quot;, Int64.Type}, {&amp;quot;SASClientOtherError&amp;quot;, Int64.Type}, {&amp;quot;ServerOtherError&amp;quot;, Int64.Type}, {&amp;quot;AnonymousServerOtherError&amp;quot;, Int64.Type}, {&amp;quot;SASServerOtherError&amp;quot;, Int64.Type}, {&amp;quot;AuthorizationError&amp;quot;, Int64.Type}, {&amp;quot;AnonymousAuthorizationError&amp;quot;, Int64.Type}, {&amp;quot;SASAuthorizationError&amp;quot;, Int64.Type}, {&amp;quot;NetworkError&amp;quot;, Int64.Type}, {&amp;quot;AnonymousNetworkError&amp;quot;, Int64.Type}, {&amp;quot;SASNetworkError&amp;quot;, Int64.Type} }),
    DuplicatePartitionKey = Table.DuplicateColumn(ChangeInitialTypes, &amp;quot;PartitionKey&amp;quot;, &amp;quot;Date&amp;quot;),
    SplitRowKey = Table.SplitColumn(DuplicatePartitionKey, &amp;quot;RowKey&amp;quot;, Splitter.SplitTextByDelimiter(&amp;quot;;&amp;quot;, QuoteStyle.Csv), {&amp;quot;Access Type&amp;quot;, &amp;quot;Transaction Type&amp;quot;}),
    ChangeFinalTypes = Table.TransformColumnTypes(SplitRowKey,{ {&amp;quot;Date&amp;quot;, type date}, {&amp;quot;Access Type&amp;quot;, type text}, {&amp;quot;Transaction Type&amp;quot;, type text} }),
    RenamePartitionKey = Table.RenameColumns(ChangeFinalTypes,{ {&amp;quot;PartitionKey&amp;quot;, &amp;quot;DateTime&amp;quot;} })
in
    RenamePartitionKey
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Blob Capacity&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let
    Source = AzureStorage.Tables(TableEndpointURL),
    BlobCapacity = Source{[Name=&amp;quot;$MetricsCapacityBlob&amp;quot;]}[Data],
    BlobCapacityExpanded = Table.ExpandRecordColumn(BlobCapacity, &amp;quot;Content&amp;quot;, {&amp;quot;Capacity&amp;quot;, &amp;quot;ContainerCount&amp;quot;, &amp;quot;ObjectCount&amp;quot;}, {&amp;quot;Capacity&amp;quot;, &amp;quot;ContainerCount&amp;quot;, &amp;quot;ObjectCount&amp;quot;}),
    ChangePartitionKeyType = Table.TransformColumnTypes(BlobCapacityExpanded,{ {&amp;quot;PartitionKey&amp;quot;,type datetime} }),
    DuplicatePartitionKey = Table.DuplicateColumn(ChangePartitionKeyType, &amp;quot;PartitionKey&amp;quot;, &amp;quot;Date&amp;quot;),
    RenamePartitionKey = Table.RenameColumns(DuplicatePartitionKey,{ {&amp;quot;PartitionKey&amp;quot;, &amp;quot;DateTime&amp;quot;} }),
    ChangeTypes = Table.TransformColumnTypes(RenamePartitionKey,{ {&amp;quot;Date&amp;quot;, type date}, {&amp;quot;Capacity&amp;quot;, Int64.Type}, {&amp;quot;ContainerCount&amp;quot;, Int64.Type}, {&amp;quot;ObjectCount&amp;quot;, Int64.Type} })
in
    ChangeTypes
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;updates-and-suggestions&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/#updates-and-suggestions&quot;&gt;Updates and suggestions&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you have any suggestions for the report (either formatting, measures, default reports) please let me know.&lt;/p&gt;
&lt;p&gt;The version of the template used when building this blog post can be downloaded &lt;a href=&quot;https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/AzureBlobStorageAnalytics.pbit&quot;&gt;here&lt;/a&gt; (the version on github may have since been updated).&lt;/p&gt;
</description>
      <pubDate>Sat, 12 Aug 2017 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2017/08/analysing-blob-storage-metrics-with-power-bi/</guid>
    </item>
    <item>
      <title>Building better OMS alerts with function apps</title>
      <link>https://tjaddison.com/blog/2017/08/building-better-oms-alerts-with-function-apps/</link>
      <description>&lt;p&gt;OMS (Operations Management Suite) allows you to easily build infrastructure alerts to tell you when a server is using a lot of CPU, low on disk space, etc., but if you&#39;ve tried to use the alerts to trigger webhooks to something like Slack you&#39;ve probably come across a pretty painful limitation - getting the name of the computer that triggered the alert can&#39;t be done dynamically, meaning you either have to settle for an alert which tells you &amp;quot;One of your computers is using a lot of CPU!&amp;quot;, or create one alert per-computer, and hardcode the computer name into the alert.&lt;/p&gt;
&lt;p&gt;We&#39;ll use a simple function app to not only report which server triggered the alert, but have far more granular control over what we report in Slack:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/building-better-oms-alerts-with-function-apps/DiZwTlkq1f-295.avif 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/building-better-oms-alerts-with-function-apps/DiZwTlkq1f-295.webp 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Sample Slack Message&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/08/building-better-oms-alerts-with-function-apps/DiZwTlkq1f-295.jpeg&quot; width=&quot;295&quot; height=&quot;37&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;If you want to skip straight to the final code it is available in &lt;a href=&quot;https://github.com/taddison/blog-oms-to-slack/tree/master/SingleFunctionCPU&quot;&gt;this GitHub repo&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;pre-requisites&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-better-oms-alerts-with-function-apps/#pre-requisites&quot;&gt;Pre-requisites&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;An OMS account (&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/operations-management-suite/operations-management-suite-overview&quot;&gt;info&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;At least one machine reporting % Processor Usage to OMS&lt;/li&gt;
&lt;li&gt;An Azure function app (&lt;a href=&quot;https://docs.microsoft.com/en-us/azure/azure-functions/functions-overview&quot;&gt;info&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;A Slack account with an incoming webhook endpoint configured (&lt;a href=&quot;https://api.slack.com/incoming-webhooks&quot;&gt;info&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Getting all these configured is beyond the scope of this blog post.&lt;/p&gt;
&lt;h2 id=&quot;configuring-the-oms-alert&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-better-oms-alerts-with-function-apps/#configuring-the-oms-alert&quot;&gt;Configuring the OMS alert&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We&#39;re going to configure an OMS alert to fire when CPU utilisation is high. The values I&#39;m using for thresholds/timings are useful in my current environment and might not be right for you - modify as required. The query we&#39;ll base our alert off is:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Perf
| where CounterName == &amp;quot;% Processor Time&amp;quot;
| summarize AggregatedValue = avg(CounterValue) by bin(TimeGenerated, 1m), Computer
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you look at this query in Log Analytics you&#39;ll see it returns the per-minute CPU utilisation for every machine linked to your OMS account. We&#39;re going to set our alert to evaluate this query every 5 minutes, and look at the prior 5 minutes of data. In our environment we want to know any time a single computer exceeds our threshold for CPU % for 3 consecutive minutes. The thresholds we&#39;ve established to trigger a warning is 75%. We also want to suppress any alerts following a trigger for 20 minutes (to prevent spam). In the OMS alert editor that looks like this:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/building-better-oms-alerts-with-function-apps/luLCgVsSHC-295.avif 295w, https://tjaddison.com/blog/2017/08/building-better-oms-alerts-with-function-apps/luLCgVsSHC-590.avif 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/building-better-oms-alerts-with-function-apps/luLCgVsSHC-295.webp 295w, https://tjaddison.com/blog/2017/08/building-better-oms-alerts-with-function-apps/luLCgVsSHC-590.webp 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/building-better-oms-alerts-with-function-apps/luLCgVsSHC-295.jpeg 295w, https://tjaddison.com/blog/2017/08/building-better-oms-alerts-with-function-apps/luLCgVsSHC-590.jpeg 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;OMS Alert Config&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/08/building-better-oms-alerts-with-function-apps/luLCgVsSHC-295.jpeg&quot; width=&quot;590&quot; height=&quot;425&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;The way alerting &amp;amp; suppression works is per-machine. This means that if two different machines are see-sawing around the threshold such that one of them is always above and the other is always below, this alert will &lt;em&gt;not fire&lt;/em&gt;. This is the behaviour we want - only alert us when a single machine stays above the threshold for 3 measurement periods. Make sure you take your measurement period into account when building alerts - if you measure CPU every 5 minutes only it won&#39;t make any sense to look at per-minute granularity.&lt;/p&gt;
&lt;p&gt;The final part of configuring the OMS alert is to configure the outbound webhook that OMS will call every time the alert fires. Note that if multiple machines trigger the alert in a single evaluation period, OMS will trigger the alert once for every machine.&lt;/p&gt;
&lt;p&gt;While testing I suggest you use &lt;a href=&quot;https://requestbin.net/&quot;&gt;RequestBin&lt;/a&gt; to capture the data that OMS sends. When configuring the webhook ensure you check the &lt;em&gt;include custom JSON payload&lt;/em&gt; option, and add the following JSON:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;IncludeSearchResults&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;WarningThreshold&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;75&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;CriticalThreshold&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;90&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The IncludeSearchResults tells OMS that we want the search results generated to be included in the webhook payload. OMS will only include the search results for the machine which has triggered the alert. You can see an example of the payload generated in &lt;a href=&quot;https://github.com/taddison/blog-oms-to-slack/blob/master/SingleFunctionCPU/cpu-payload.json&quot;&gt;this payload file&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The warning &amp;amp; critical threshold values are provided for use in function app, and we&#39;ll use them to allow us to have a single OMS alert for both warning &amp;amp; critical, rather than having to create two alerts.&lt;/p&gt;
&lt;p&gt;When correctly configured your OMS webhook config will look like this (though with your only RequestBin URL/function app URL):&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/building-better-oms-alerts-with-function-apps/7Dz9RRBjQx-295.avif 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/08/building-better-oms-alerts-with-function-apps/7Dz9RRBjQx-295.webp 295w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;OMS Webhook Config&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/08/building-better-oms-alerts-with-function-apps/7Dz9RRBjQx-295.jpeg&quot; width=&quot;295&quot; height=&quot;234&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;You can test the webhook, though OMS will not run the query and include the search results. If you want to see what the search results look like you&#39;ll need to let the alert fire - set your threshold to something low (e.g. 1%) and the alert will fire within a few minutes.&lt;/p&gt;
&lt;h2 id=&quot;building-the-function-app&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-better-oms-alerts-with-function-apps/#building-the-function-app&quot;&gt;Building the Function app&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Create a new function app using C# Webhook template. The template also creates HTTP output, though we&#39;re not going to use that so you can delete it, leaving your function app with a HTTP trigger only.&lt;/p&gt;
&lt;p&gt;We&#39;ll create a few helper classes to work with the incoming data from OMS, as well as create our Slack webhook. To build the OMS classes I used &lt;a href=&quot;http://json2csharp.com/&quot;&gt;json2csharp.com&lt;/a&gt;, and pasted into the JSON payload from OMS. This generates a pretty verbose class, which you can strip back to the below.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;OMSPayload&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;/span&gt; WarningThreshold &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;int&lt;/span&gt;&lt;/span&gt; CriticalThreshold &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;SearchResults&lt;/span&gt; SearchResults &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SearchResults&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;List&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Table&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; Tables &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Table&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; TableName &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;List&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;Column&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; Columns &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;List&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;List&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; Rows &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Column&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; ColumnName &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; DataType &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; ColumnType &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In order to send a message to Slack we&#39;ll use a fairly simple class:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SlackMessage&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; text &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; channel &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;/span&gt; username &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The function app is going to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Parse the OMS JSON into the classes above&lt;/li&gt;
&lt;li&gt;Determine if we&#39;re in a warning or critical state&lt;/li&gt;
&lt;li&gt;Extract the name of the computer alerting&lt;/li&gt;
&lt;li&gt;Build a message with the computer name and some simple metrics&lt;/li&gt;
&lt;li&gt;Send the message to Slack&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The full text of the function (assuming the default input name of req) is below. Note this excludes the class definitions above, which would normally go at the bottom of the file.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token preprocessor property&quot;&gt;#r &lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Newtonsoft.Json&quot;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;System&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Net&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;System&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Collections&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;Newtonsoft&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;token namespace&quot;&gt;System&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;Task&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;HttpRequestMessage&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TraceWriter&lt;/span&gt; log&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; slackUri &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://requestb.in/su78d1su&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; slackChannel &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;#webhook-tests&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; slackUsername &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;OMS&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; criticalMeasurementCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Content&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-method&quot;&gt;&lt;span class=&quot;token function&quot;&gt;ReadAsAsync&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;OMSPayload&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; warningThreshold &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;?.&lt;/span&gt;WarningThreshold &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;75&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; criticalThreshold &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;?.&lt;/span&gt;CriticalThreshold &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;90&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; aggregatedResults &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SearchResults&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Tables&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Rows&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;GroupBy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ToString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;g &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  Computer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; g&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Key&lt;br /&gt;                                                &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;Average &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; g&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Average&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; Double&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Parse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ToString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                                                &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;Warning &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; g&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;  Double&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Parse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ToString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; warningThreshold&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;                                                &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;Critical &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; g&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;  Double&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Parse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ToString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; criticalThreshold&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; message &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Empty&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; critical &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; result &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; aggregatedResults&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        message &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt;  &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;$&quot; - &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Computer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;:[&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Warning&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; &gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;warningThreshold&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;% | &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Critical&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; &gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;criticalThreshold&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;% | &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Average&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token format-string&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;P0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; avg]&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Critical &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; criticalMeasurementCount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            critical &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    message &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token interpolation-string&quot;&gt;&lt;span class=&quot;token string&quot;&gt;$&quot;Infra - CPU &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token expression language-csharp&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;critical &lt;span class=&quot;token punctuation&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Critical&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Warning&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;using&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; client &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;HttpClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; payload &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;SlackMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; channel &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; slackChannel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; username &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; slackUsername &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; hook &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token constructor-invocation class-name&quot;&gt;StringContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;JsonConvert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;SerializeObject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;payload&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Encoding&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;UTF8&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;PostAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;slackUri&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; hook&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;CreateResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;HttpStatusCode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;OK&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If all of the code and class definitions are present in the same file in the function app (&lt;code&gt;run.csx&lt;/code&gt;) you should now be able to test this with the sample payload from OMS, or point OMS directly at the function.&lt;/p&gt;
&lt;p&gt;The format of the message produced is:&lt;/p&gt;
&lt;p&gt;Infra - CPU &lt;strong&gt;Severity&lt;/strong&gt; - &lt;strong&gt;ServerName&lt;/strong&gt;:[&lt;strong&gt;WarnCount&lt;/strong&gt; &amp;gt;&lt;strong&gt;Warn&lt;/strong&gt;% | &lt;strong&gt;CritCount&lt;/strong&gt; &amp;gt;&lt;strong&gt;Crit&lt;/strong&gt;% | &lt;strong&gt;Mean&lt;/strong&gt; % avg]&lt;/p&gt;
&lt;p&gt;Where WarnCount is the number of times in the sampling period that the metric exceeded Warn, and CritCount the same for Crit. In the below example message we can see the severity is critical, and that 5/5 measures exceeded the warning threshold of 60%, and 5/5 measures exceeded the critical threshold of 90%. This server doesn&#39;t look too healthy!&lt;/p&gt;
&lt;p&gt;Infra - CPU Critical - Server1:[5 &amp;gt;60% | 5 &amp;gt;90% | 100 % avg]&lt;/p&gt;
&lt;p&gt;You&#39;ll notice the code also uses a RequestBin for testing the Slack alerts. When you&#39;re done testing both the OMS webhook URL and the Uri specified in the Function app should point to their live endpoints.&lt;/p&gt;
&lt;h2 id=&quot;next-steps&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/08/building-better-oms-alerts-with-function-apps/#next-steps&quot;&gt;Next steps&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As configured, the solution will now start routing Slack alerts to your chosen channel, enriched with information from the search results included with each alert. In the future we&#39;ll extend the solution to support fan-out (1 alert notifying multiple channels), notifications (conditional @channel to get attention), and decoupling the notification from the trigger.&lt;/p&gt;
&lt;p&gt;Updated 2017-09-08 for new OMS query language/search payload.&lt;/p&gt;
</description>
      <pubDate>Sun, 06 Aug 2017 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2017/08/building-better-oms-alerts-with-function-apps/</guid>
    </item>
    <item>
      <title>Using the SQL Infrastructure Tests repo in your environment</title>
      <link>https://tjaddison.com/blog/2017/07/using-the-sql-infrastructure-tests-repo-in-your-environment/</link>
      <description>&lt;blockquote&gt;
&lt;p&gt;Update 2017-03-12: The &lt;a href=&quot;https://github.com/taddison/SQLChecks&quot;&gt;SQLChecks&lt;/a&gt; library builds on the ideas in this post and delivers an upgraded version of SQLInfrastructureTests. &lt;a href=&quot;https://tjaddison.com/blog/2017/12/using-the-sqlchecks-library-for-sql-server-configuration-management/&quot;&gt;This blog post&lt;/a&gt; discusses how to use the library.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/taddison/SQLInfrastructureTests&quot;&gt;SQL Infrastructure Tests&lt;/a&gt; repo is generic and knows nothing about your infrastructure. This makes it very easy to clone and use anywhere, but also means you need to provide some configuration information to actually make it useful in your environment. Typically you want that environment specific information to also be in source control, you might be tempted to create a fork of the repo and make customisations for your environment.&lt;/p&gt;
&lt;p&gt;This nearly always ends in pain when you need to pull updates from the repository. A better pattern is to create helper repositories (which would be privately version controlled). These are where you make all your environment specific changes (your server names, etc.). This has the added advantage of making it much harder to accidentally publish environment specific information to GitHub (say if you were to accidentally create a PR of your private fork…).&lt;/p&gt;
&lt;p&gt;The public repo we&#39;re consuming is &lt;a href=&quot;https://github.com/taddison/SQLInfrastructureTests&quot;&gt;SQLInfrastructureTests&lt;/a&gt;, and the private repository that we&#39;ll be using is &lt;a href=&quot;https://github.com/taddison/SQLInfrastructureTests-Helper&quot;&gt;SQLInfrastructureTests-Helper&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The Install.ps1 script lives in our helper and will either clone the repository (if it doesn&#39;t exist), or update it if does.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Install.ps1&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$repoName&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;SQLInfrastructureTests&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$repoUri&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;https://github.com/taddison/&lt;span class=&quot;token variable&quot;&gt;$repoName&lt;/span&gt;.git&quot;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Test-Path&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;path &lt;span class=&quot;token string&quot;&gt;&quot;../&lt;span class=&quot;token variable&quot;&gt;$repoName&lt;/span&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    git clone &lt;span class=&quot;token variable&quot;&gt;$repoUri&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;../&lt;span class=&quot;token variable&quot;&gt;$repoName&lt;/span&gt;&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;Set-Location&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Path &lt;span class=&quot;token string&quot;&gt;&quot;../&lt;span class=&quot;token variable&quot;&gt;$repoName&lt;/span&gt;&quot;&lt;/span&gt;&lt;br /&gt;    git pull&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The RunTests.ps1 script then executes the pester tests that exist in the public repository. Because this file lives in our private repository, you would be able to use your own server names (or pull them from a CMS), and optionally specify subsets of tests to execute against different servers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RunTests.ps1&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$servers&lt;/span&gt; = @&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;localhost&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$repoName&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;SQLInfrastructureTests&quot;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# Run all the tests&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Invoke-Pester&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Script @&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;Path=&lt;span class=&quot;token string&quot;&gt;&quot;../&lt;span class=&quot;token variable&quot;&gt;$repoName&lt;/span&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;Parameters= @&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;servers=&lt;span class=&quot;token variable&quot;&gt;$servers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# Run a single test&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;Invoke-Pester&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Script @&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;Path=&lt;span class=&quot;token string&quot;&gt;&quot;../&lt;span class=&quot;token variable&quot;&gt;$repoName&lt;/span&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;Parameters= @&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;servers=&lt;span class=&quot;token variable&quot;&gt;$servers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; `&lt;br /&gt;              &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;TestName &lt;span class=&quot;token string&quot;&gt;&quot;*SQL Agent*&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The final line of the script above shows how you can use the TestName parameter of &lt;a href=&quot;https://github.com/pester/Pester/wiki/Invoke-Pester&quot;&gt;Invoke-Pester&lt;/a&gt; to limit the tests which are executed. Pester also supports tagging your tests, and this can be combined with name filtering to give your even more fine grained control over what you want to execute.&lt;/p&gt;
&lt;p&gt;The idea of using helper repos can be applied fairly generally, and is useful anywhere you want to consume an open source project (and get the latest updates easily). I&#39;ve also used this technique to have a single &#39;SQL Tools&#39; repo in version control, which any developer can check out and run the install script on to get the latest version of SQL scripts &amp;amp; tools like the &lt;a href=&quot;https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit&quot;&gt;First Responder Toolkit&lt;/a&gt;.&lt;/p&gt;
</description>
      <pubDate>Sun, 09 Jul 2017 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2017/07/using-the-sql-infrastructure-tests-repo-in-your-environment/</guid>
    </item>
    <item>
      <title>Checking SQL Agent job ownership with Pester</title>
      <link>https://tjaddison.com/blog/2017/04/checking-sql-agent-job-ownership-with-pester/</link>
      <description>&lt;p&gt;Ensuring your jobs are all owned by SA is a best practice I&#39;ve used to help minimise the chance of an SA job not running correctly due to the owners login being disabled, or there being an issue with authentication.&lt;/p&gt;
&lt;p&gt;There are plenty of ways of going about this, though the most flexible I&#39;ve found so far is making it an automated infrastructure test with Pester. If you&#39;re not familiar with Pester I encourage you to check out the &lt;a href=&quot;https://github.com/pester/Pester&quot;&gt;Pester Github site&lt;/a&gt;, and then for more SQL specific details browse the Pester category of posts by &lt;a href=&quot;https://sqldbawithabeard.com/tag/pester/&quot;&gt;SQL DBA With a Beard&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once Pester is configured, the simplest way to execute a test is to run the following.&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$query&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;select count(*) as Jobs from msdb.dbo.sysjobs as j where j.owner_sid &amp;lt;&gt; 0x01&quot;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Describe &lt;span class=&quot;token string&quot;&gt;&quot;SQL Agent on localhost&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    It &lt;span class=&quot;token string&quot;&gt;&quot;has jobs only owned by sa&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Invoke-Sqlcmd&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ServerInstance localhost &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Query &lt;span class=&quot;token variable&quot;&gt;$query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Jobs &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; Should Be 0&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This executes the query against our localhost, and compares the result (count(*) aliased as Jobs) against the expected value of 0. When it works we get the following output:&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/04/checking-sql-agent-job-ownership-with-pester/ICKmJxVCvq-290.avif 290w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/04/checking-sql-agent-job-ownership-with-pester/ICKmJxVCvq-290.webp 290w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Successful Pester test&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/04/checking-sql-agent-job-ownership-with-pester/ICKmJxVCvq-290.jpeg&quot; width=&quot;290&quot; height=&quot;49&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;If there are any jobs which are not owned by SA then we&#39;ll be told how many there are.&lt;/p&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/04/checking-sql-agent-job-ownership-with-pester/WJewWKuJLL-295.avif 295w, https://tjaddison.com/blog/2017/04/checking-sql-agent-job-ownership-with-pester/WJewWKuJLL-590.avif 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/04/checking-sql-agent-job-ownership-with-pester/WJewWKuJLL-295.webp 295w, https://tjaddison.com/blog/2017/04/checking-sql-agent-job-ownership-with-pester/WJewWKuJLL-590.webp 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/jpeg&quot; srcset=&quot;https://tjaddison.com/blog/2017/04/checking-sql-agent-job-ownership-with-pester/WJewWKuJLL-295.jpeg 295w, https://tjaddison.com/blog/2017/04/checking-sql-agent-job-ownership-with-pester/WJewWKuJLL-590.jpeg 590w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Failed Pester test&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/04/checking-sql-agent-job-ownership-with-pester/WJewWKuJLL-295.jpeg&quot; width=&quot;590&quot; height=&quot;84&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;To really leverage this you&#39;ll want to target multiple servers. To do that from a single script we can provide a hard-coded list of servers and then call the test in a loop.&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$query&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;select count(*) as Jobs from msdb.dbo.sysjobs as j where j.owner_sid &amp;lt;&gt; 0x01&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$servers&lt;/span&gt; = @&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;localhost&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$server&lt;/span&gt; in &lt;span class=&quot;token variable&quot;&gt;$servers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    Describe &lt;span class=&quot;token string&quot;&gt;&quot;SQL Agent on &lt;span class=&quot;token variable&quot;&gt;$server&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        It &lt;span class=&quot;token string&quot;&gt;&quot;has jobs only owned by sa&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Invoke-Sqlcmd&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ServerInstance localhost &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Query &lt;span class=&quot;token variable&quot;&gt;$query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Jobs &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; Should Be 0&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;picture&gt;&lt;source type=&quot;image/avif&quot; srcset=&quot;https://tjaddison.com/blog/2017/04/checking-sql-agent-job-ownership-with-pester/h0PJpEWSAw-290.avif 290w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;source type=&quot;image/webp&quot; srcset=&quot;https://tjaddison.com/blog/2017/04/checking-sql-agent-job-ownership-with-pester/h0PJpEWSAw-290.webp 290w&quot; sizes=&quot;(max-width: 768px) 100vw, 768px&quot; /&gt;&lt;img alt=&quot;Multiple Pester tests&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://tjaddison.com/blog/2017/04/checking-sql-agent-job-ownership-with-pester/h0PJpEWSAw-290.jpeg&quot; width=&quot;290&quot; height=&quot;77&quot; /&gt;&lt;/picture&gt;&lt;/p&gt;
&lt;p&gt;The list of servers could come from a text file, or a &lt;a href=&quot;https://docs.microsoft.com/en-us/sql/relational-databases/administer-multiple-servers-using-central-management-servers&quot;&gt;CMS Server&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;moving-from-a-single-script-to-a-solution&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/04/checking-sql-agent-job-ownership-with-pester/#moving-from-a-single-script-to-a-solution&quot;&gt;Moving from a single script to a solution&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As you go on to write more tests you will probably want to take advantage of Pester&#39;s ability to isolate tests in test files (with the .tests.ps1 extension) and want to run them all. Typically you&#39;ll want to manage the list of servers you execute the test against outside of the test itself. What I suggest is making the server list a common parameter for all your SQL test files, and then populating the server list in a &#39;runner&#39; class.&lt;/p&gt;
&lt;p&gt;In the below example you&#39;ll see we connect to a CMS to pull the list of servers to execute against (based on a group called &#39;Instances to Test&#39;). The query has also been modified to ignore jobs in the &#39;Report Server&#39; category, which is what SSRS uses when it creates jobs to support subscriptions (none of which are ever owned by SA).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RunTests.ps1&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$cms&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;localhost&#92;CMS&quot;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$servers&lt;/span&gt; = &lt;span class=&quot;token function&quot;&gt;invoke-sqlcmd&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Server &lt;span class=&quot;token variable&quot;&gt;$cms&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Query &lt;span class=&quot;token string&quot;&gt;&quot;&lt;br /&gt;SELECT ssrsi.server_name&lt;br /&gt;from msdb.dbo.sysmanagement_shared_registered_servers_internal as ssrsi&lt;br /&gt;join msdb.dbo.sysmanagement_shared_server_groups_internal as sssgi&lt;br /&gt;on sssgi.server_group_id = ssrsi.server_group_id&lt;br /&gt;where sssgi.name = &#39;Instances to Test&#39;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Select-Object&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ExpandProperty server_name&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;invoke-pester&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Script @&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;Path=&lt;span class=&quot;token string&quot;&gt;&#39;.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;Parameters= @&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;serverList=&lt;span class=&quot;token variable&quot;&gt;$servers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;SQLAgent.tests.ps1&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-powershell&quot;&gt;&lt;code class=&quot;language-powershell&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;Param&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token namespace&quot;&gt;[object]&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$servers&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$query&lt;/span&gt; = &lt;span class=&quot;token string&quot;&gt;&quot;&lt;br /&gt;select count(*) as Jobs&lt;br /&gt;from msdb.dbo.sysjobs as j&lt;br /&gt;join msdb.dbo.syscategories as c&lt;br /&gt;on c.category_id = j.category_id&lt;br /&gt;where j.owner_sid &amp;lt;&gt; 0x01&lt;br /&gt;and c.Name &amp;lt;&gt; &#39;Report Server&#39;&quot;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$server&lt;/span&gt; in &lt;span class=&quot;token variable&quot;&gt;$servers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    Describe &lt;span class=&quot;token string&quot;&gt;&quot;SQL Agent on &lt;span class=&quot;token variable&quot;&gt;$server&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        It &lt;span class=&quot;token string&quot;&gt;&quot;has jobs only owned by sa&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Invoke-Sqlcmd&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;ServerInstance &lt;span class=&quot;token variable&quot;&gt;$server&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;Query &lt;span class=&quot;token variable&quot;&gt;$query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Jobs &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; Should Be 0&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you create additional infrastructure tests you can drop them in the folder and Pester will automatically run them on your list of target servers. I&#39;ve got a repository which contains some example tests on &lt;a href=&quot;https://github.com/taddison/SQLInfrastructureTests&quot;&gt;Github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Note that the test we have coded for looks to ensure all jobs are owned by SA. To workaround the issues described at the start of the post (e.g. can&#39;t authenticate a user account to run a job) we should say that &lt;em&gt;user accounts&lt;/em&gt; shouldn&#39;t own jobs. In my current environment that translates to all jobs must be owned by SA. In your environment you might have a dedicated SQL account for jobs (or something similar), and so you may wish to modify this check.&lt;/p&gt;
&lt;h2 id=&quot;additional-reading&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2017/04/checking-sql-agent-job-ownership-with-pester/#additional-reading&quot;&gt;Additional Reading&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.brentozar.com/blitz/&quot;&gt;sp_Blitz&lt;/a&gt; One of the checks sp_Blitz currently implements is &lt;a href=&quot;https://www.brentozar.com/blitz/jobs-owned-by-user-accounts/&quot;&gt;Jobs Owned by User Accounts&lt;/a&gt;. If you&#39;re running this regularly it&#39;ll help catch the SQL Agent job issue, as well as many others.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/equelin/Format-Pester&quot;&gt;Format-Pester&lt;/a&gt; Take the output of Invoke-Pester and produce self-contained HTML summary pages.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/taddison/tsqlScheduler&quot;&gt;tSQLScheduler&lt;/a&gt; Github project used to create agent jobs automatically from data, ensuring they&#39;re always created as SA.&lt;/p&gt;
</description>
      <pubDate>Sat, 22 Apr 2017 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2017/04/checking-sql-agent-job-ownership-with-pester/</guid>
    </item>
    <item>
      <title>What makes an excellent database engineer?</title>
      <link>https://tjaddison.com/blog/2016/10/what-makes-an-excellent-database-engineer/</link>
      <description>&lt;p&gt;The question of what makes an excellent database engineer is one I&#39;m always asking myself and increasingly find myself answering for others. The question typically isn&#39;t phrased quite so directly, and instead shows up in a variety of discussions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In 1:1s this might be part of a discussion on long-term career goals and progress against them&lt;/li&gt;
&lt;li&gt;During the recruiting process some of the best candidates will probe to understand how I define greatness in that role&lt;/li&gt;
&lt;li&gt;When working with other leads we want to try and understand why certain team members are great, and look to see what behaviours we want to encourage elsewhere&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While the specifics will always vary with the individual, the team, and the platform, I believe that the below fairly accurately captures what I currently think of as the key traits &amp;amp; beliefs an excellent database engineer should possess:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deep technical mastery of one or more areas&lt;/li&gt;
&lt;li&gt;Broad knowledge of the platform and the ecosystem it works in&lt;/li&gt;
&lt;li&gt;Ownership of problems &amp;amp; their solutions end to end&lt;/li&gt;
&lt;li&gt;Does not tolerate badness&lt;/li&gt;
&lt;li&gt;Treats the system as a customer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is largely based on the role of a database engineer who is both a developer, an administrator, and an operations guy (outside of some specialist exceptions I don&#39;t think the dedicated role of DBA really has a future - perhaps a subject for another post). I primarily work in the Microsoft ecosystem but I believe the detail below applies for any ecosystem, and probably more generally for any software engineering role.&lt;/p&gt;
&lt;p&gt;Each item is discussed in more detail below.&lt;/p&gt;
&lt;h2 id=&quot;deep-technical-mastery-of-one-or-more-areas&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2016/10/what-makes-an-excellent-database-engineer/#deep-technical-mastery-of-one-or-more-areas&quot;&gt;Deep technical mastery of one or more areas&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A whole heap of other qualities are rolled up into this goal. The fact this goal is never actually finished (can anyone ever say they&#39;re &lt;em&gt;finished&lt;/em&gt; learning about query optimisation? How long would that statement be valid for?) means you need to be both humble in accepting you have something to learn, and curious enough to keep you digging into that area.&lt;/p&gt;
&lt;p&gt;Each additional level of mastery means you&#39;re now able to teach that area to other people (teaching/speaking/writing about an area at level N requires level N+1 knowledge), that you&#39;re able to work in that area with reduced effort and increased confidence, and that any project which interacts with that area will benefit from your input.&lt;/p&gt;
&lt;p&gt;Database engineering excites me so much as it exposes a huge surface area for learning - you&#39;re at the confluence of academic research, hardware &amp;amp; software advances, product advances, as well as the disciplines of programming, systems architecture, and system administration. On top of all that sits the solution you&#39;ll build to solve a real problem, and knowing the foundations of what you build is going to help you deliver a better project (cost, performance, reliability).&lt;/p&gt;
&lt;h2 id=&quot;broad-knowledge-of-the-platform-and-the-ecosystem-it-works-in&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2016/10/what-makes-an-excellent-database-engineer/#broad-knowledge-of-the-platform-and-the-ecosystem-it-works-in&quot;&gt;Broad knowledge of the platform and the ecosystem it works in&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can&#39;t solve a problem using a tool you don&#39;t even know exists. That&#39;s a rough analogy but it holds even when you start to stretch that up to the system level. Consider the following changes SQL 2016 has brought (or will bring soon in some cases), which eliminate problems ranging from small code changes to entire systems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Native string split function&lt;/li&gt;
&lt;li&gt;Query Store&lt;/li&gt;
&lt;li&gt;Always Encrypted&lt;/li&gt;
&lt;li&gt;Polybase&lt;/li&gt;
&lt;li&gt;Power BI embedded in SSRS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&#39;ve been involved in projects which have built, maintained, and ultimately deprecated various solutions which are all solved far more elegantly by something which is now part of the &#39;boxed&#39; product. It&#39;s so easy to delegate the responsibility of knowing this to architects/consultants, but the art of the possible is something each engineer needs to own for herself.&lt;/p&gt;
&lt;p&gt;The key to brilliance when being broad isn&#39;t knowing everything that is happening (impossible) or the fine details of everything you learn (also impossible), but to have a model of the system you&#39;re trying to understand, and a method for keeping up to date or digging deeper.&lt;/p&gt;
&lt;p&gt;Very briefly, this might mean you split your ecosystem into a few areas (perhaps by product, by problem solved, by team). For each area you would think about how you&#39;d keep abreast of significant happenings in those areas (that might be colleagues, conferences, blogs, etc.). Finally you&#39;d assign yourself some kind of depth gauge for each area - if you&#39;re spending no time keeping up to date with .Net and 100% of your data access happens in .Net, you&#39;d want to be very careful before relying on any old assumptions.&lt;/p&gt;
&lt;h2 id=&quot;ownership-of-problems-and-their-solutions-end-to-end&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2016/10/what-makes-an-excellent-database-engineer/#ownership-of-problems-and-their-solutions-end-to-end&quot;&gt;Ownership of problems &amp;amp; their solutions end to end&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First and foremost we&#39;re problem solvers. The best problem solvers I&#39;ve worked with have all shared the same fundamental trait - &lt;em&gt;they own the problem&lt;/em&gt;. I&#39;d hesitate to call the following a framework, but I&#39;ve seen very similar steps executed by some of the most respected problem solvers I&#39;ve seen in action:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Understand the problem
&lt;ul&gt;
&lt;li&gt;Why are we solving this problem&lt;/li&gt;
&lt;li&gt;What will we gain&lt;/li&gt;
&lt;li&gt;How will we know if we&#39;ve solved the problem&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Understand how the problem might be solved
&lt;ul&gt;
&lt;li&gt;Explore a few different options, even if unfeasible now (what &lt;em&gt;might&lt;/em&gt; another solution look like?)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Understand how this particular implementation will work
&lt;ul&gt;
&lt;li&gt;Any weaknesses, positive-side-effects&lt;/li&gt;
&lt;li&gt;What are the unknowns&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Identify all the stakeholders
&lt;ul&gt;
&lt;li&gt;Who needs to know before we start/as we progress/when we&#39;re done&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Own the problem until it&#39;s solved/no longer a priority&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Implicit in the above (but explicit in the title of this section) is that you&#39;re not just taking ownership of the data/data-tier. This can really manifest when exploring other options - you might not be empowered to make process or application changes, but if fixing bad data can be better solved by modifying the application or business processes that input the data you owe it to the data to at least outline that option. I&#39;ll talk more about the data (and the system) as a customer later.&lt;/p&gt;
&lt;h2 id=&quot;do-not-tolerate-badness&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2016/10/what-makes-an-excellent-database-engineer/#do-not-tolerate-badness&quot;&gt;Do not tolerate badness&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Unpacking that a bit, there are two ways in which this tendency will typically show up when working with an exceptional engineer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;She ships high quality solutions (quality being not-bad)&lt;/li&gt;
&lt;li&gt;When she discovers badness she has an almost pathological desire to fix it&lt;/li&gt;
&lt;li&gt;Any system she works on typically benefits from her attention in either uncovering or fixing badness&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Obviously terms like badness and quality are subjective, and even when defined there is a scale. It gets even worse when you layer on the fact that your primary goal will typically be to ship, and so stopping to fix every bit of badness you find would leave you paralysed and unable to deliver anything. Cutting this Gordian knot with the appropriate degree of pragmatism is a lifelong challenge for any engineer that values shipping &amp;amp; disdains badness.&lt;/p&gt;
&lt;p&gt;Practically speaking, you should understand both personally and as a team what constitutes badness, and then call it out whenever you see it. Given your wide range of customers (see the next point) there will invariably be badness in your systems - how you choose to deal with it and when will shape the future of your system.&lt;/p&gt;
&lt;p&gt;Consider an example of a backup failure. Standard response might be to re-run the backup. A more curious engineer might dig and perform some root-cause analysis. A great engineer might automate the response. An exceptional engineer might keep digging until she discovers the underlying badness that leads to the backup failure and do what she can to fix it, or expose the issue to the relevant team (whose problem would the SAN backup target running out of space be?).&lt;/p&gt;
&lt;h2 id=&quot;treat-the-system-as-a-customer&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2016/10/what-makes-an-excellent-database-engineer/#treat-the-system-as-a-customer&quot;&gt;Treat the system as a customer&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While everyone may care about the data, the scalability, and the performance of the [database] system as it pertains to their application, as a data engineer you are ultimately accountable for all of those concerns, for every application. As you&#39;re making changes you should not only be thinking about the end-customer and your internal customers (colleagues), but also the system that you&#39;re modifying &amp;amp; maintaining.&lt;/p&gt;
&lt;p&gt;Building a healthy level of scepticism will set you in good stead for ensuring you keep your data systems correct (free from corruption), available, and performant. As the feature comes in which is going to add one call per second to some component, you&#39;ll be thinking about what happens at one and two orders of magnitude above that (and if you don&#39;t, who will?). When you get access to your SQL Azure instance that supports 1000 TPS, you&#39;ll be the one verifying that and finding out what happens when you exceed that number - perhaps even coming up with a list of options for what you could do in that event.&lt;/p&gt;
&lt;p&gt;There are many ways a given system can start to go south, most of which won&#39;t manifest as an application issue until it is too late. This means you and your team have to monitor and be responsible for understanding the state of the system independent of the applications they support. Your goal should always be to have the system alert you (or self-heal) so the problem is solved before it bubbles up to the application.&lt;/p&gt;
&lt;p&gt;Unlike most other customers the system doesn&#39;t have a way of making these problems known (colleagues and customers are typically adept at voicing their concerns), so in treating the system as a customer you have to ensure that you&#39;ve put the appropriate structures in place to get useful feedback and insight into the current/historic state of the system.&lt;/p&gt;
&lt;h2 id=&quot;summary-and-additional-reading&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;hover:after:content-[&#39;_🔗&#39;] text-inherit&quot; href=&quot;https://tjaddison.com/blog/2016/10/what-makes-an-excellent-database-engineer/#summary-and-additional-reading&quot;&gt;Summary &amp;amp; Additional Reading&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Excellence doesn&#39;t happen by accident - it is something which you have to continually and deliberately work towards. I&#39;ve outlined the template I currently use when having discussions about excellence in database engineering with others or for my own introspection. The list is really just a jumping-off point, and I hope to continue having interesting &amp;amp; challenging conversations with brilliant people about what excellence looks like to them.&lt;/p&gt;
&lt;p&gt;Below are a handful of interesting articles/books which served as inspiration for parts of this post. If you have any questions or thoughts about what makes an excellent engineer (database or otherwise) drop me a line or leave a comment below.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.principles.com/&quot;&gt;Principles&lt;/a&gt; - particularly principle &lt;a href=&quot;https://www.principles.com/#Principle-138&quot;&gt;138&lt;/a&gt;, which has made its way verbatim to my point &#39;Do not tolerate badness&#39;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://landing.google.com/sre/&quot;&gt;Site Reliability Engineering&lt;/a&gt; - Epitomises what I think exceptional operations looks like, and what anyone servicing a shared system (like maybe a database) should aspire to&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.valvesoftware.com/company/Valve_Handbook_LowRes.pdf&quot;&gt;Valve Staff Handbook&lt;/a&gt; (particularly pages 39-46) - Calls out T shaped individuals (breadth &amp;amp; depth) and hits a lot of the right notes on hiring for excellence&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://exadat.co.uk/&quot;&gt;Chris Adkin&lt;/a&gt;, &lt;a href=&quot;https://sqlperformance.com/author/paulwhitenzgmail-com&quot;&gt;Paul White&lt;/a&gt;, &lt;a href=&quot;https://blogs.msdn.microsoft.com/bobsql/&quot;&gt;Bob &amp;amp; Bob&lt;/a&gt; - When someone asks me for an example of an expert in a few different domains these are some of the blogs I send them&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.hanselman.com/blog/ImAPhonyAreYou.aspx&quot;&gt;Impostor syndrome&lt;/a&gt; - Great article with a wonderful closing quote that only becomes increasingly relevant the more you learn. Excellence is a tall order, it&#39;s ok to feel a little overwhelmed by it all&lt;/p&gt;
</description>
      <pubDate>Sun, 09 Oct 2016 00:00:00 +0000</pubDate>
      <dc:creator>Timothy Addison</dc:creator>
      <guid>https://tjaddison.com/blog/2016/10/what-makes-an-excellent-database-engineer/</guid>
    </item>
  </channel>
</rss>