<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Michael Salaverry</title>
    <description>The latest articles on DEV Community by Michael Salaverry (@barakplasma).</description>
    <link>https://dev.to/barakplasma</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F67064%2F0cebd8d3-d92d-435b-abff-4e431da860ab.jpeg</url>
      <title>DEV Community: Michael Salaverry</title>
      <link>https://dev.to/barakplasma</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/barakplasma"/>
    <language>en</language>
    <item>
      <title>FastGRPC - FastAPI with the speed of gRPC</title>
      <dc:creator>Michael Salaverry</dc:creator>
      <pubDate>Mon, 30 Mar 2026 19:26:29 +0000</pubDate>
      <link>https://dev.to/barakplasma/fastgrpc-fastapi-with-the-speed-of-grpc-3g03</link>
      <guid>https://dev.to/barakplasma/fastgrpc-fastapi-with-the-speed-of-grpc-3g03</guid>
      <description>&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/barakplasma" rel="noopener noreferrer"&gt;
        barakplasma
      &lt;/a&gt; / &lt;a href="https://github.com/barakplasma/FastGRPC" rel="noopener noreferrer"&gt;
        FastGRPC
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;FastGRPC&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/barakplasma/FastGRPC/actions/workflows/ci.yml" rel="noopener noreferrer"&gt;&lt;img src="https://github.com/barakplasma/FastGRPC/actions/workflows/ci.yml/badge.svg" alt="CI"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Add gRPC to your FastAPI app in one line. No protobuf. No config. ~2× faster than HTTP/JSON.&lt;/strong&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The Pitch&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;You already have a FastAPI app. FastGRPC reads it at startup and gives you a fully working gRPC server — same handlers, same Pydantic models, zero new files to write or maintain.&lt;/p&gt;
&lt;div class="highlight highlight-source-python notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s1"&gt;fastgrpc&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-v"&gt;FastGRPC&lt;/span&gt;

&lt;span class="pl-c"&gt;# Your existing FastAPI app, unchanged&lt;/span&gt;
&lt;span class="pl-s1"&gt;app&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;FastAPI&lt;/span&gt;()

&lt;span class="pl-en"&gt;@&lt;span class="pl-s1"&gt;app&lt;/span&gt;.&lt;span class="pl-c1"&gt;get&lt;/span&gt;(&lt;span class="pl-s"&gt;"/items/{item_id}"&lt;/span&gt;, &lt;span class="pl-s1"&gt;response_model&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-v"&gt;Item&lt;/span&gt;)&lt;/span&gt;
&lt;span class="pl-k"&gt;async&lt;/span&gt; &lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;get_item&lt;/span&gt;(&lt;span class="pl-s1"&gt;item_id&lt;/span&gt;: &lt;span class="pl-smi"&gt;int&lt;/span&gt;) &lt;span class="pl-c1"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="pl-smi"&gt;Item&lt;/span&gt;:
    ...

&lt;span class="pl-c"&gt;# One line to add gRPC on port 50051&lt;/span&gt;
&lt;span class="pl-en"&gt;FastGRPC&lt;/span&gt;(&lt;span class="pl-s1"&gt;app&lt;/span&gt;, &lt;span class="pl-s1"&gt;grpc_port&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-c1"&gt;50051&lt;/span&gt;)&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;That's the entire migration.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Performance&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Benchmarked on the same FastAPI app, same handler logic, 30,000 requests over ~1 minute with persistent connections (how production clients work):&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;HTTP/JSON&lt;/th&gt;
&lt;th&gt;gRPC&lt;/th&gt;
&lt;th&gt;Speedup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Small payload (3 fields)&lt;/td&gt;
&lt;td&gt;5.38 ms&lt;/td&gt;
&lt;td&gt;1.18 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4.6×&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Large payload (~50 fields, nested)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;…&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/barakplasma/FastGRPC" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Chrome Reading List export</title>
      <dc:creator>Michael Salaverry</dc:creator>
      <pubDate>Sun, 18 Jun 2023 11:25:20 +0000</pubDate>
      <link>https://dev.to/barakplasma/chrome-reading-list-export-1h</link>
      <guid>https://dev.to/barakplasma/chrome-reading-list-export-1h</guid>
      <description>&lt;p&gt;The easiest way to export the chrome reading list is to use &lt;a href="https://takeout.google.com/" rel="noopener noreferrer"&gt;takeout.google.com&lt;/a&gt; and to export the Chrome details. On my Google workspace account, this wasn't possible.&lt;/p&gt;

&lt;p&gt;Instead, I navigated to &lt;a href="https://dev.tochrome://sync-internals/"&gt;chrome://sync-internals/&lt;/a&gt; on the sync node browser tab and ran this javascript code in the console:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#sync-node-tree&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#tree-item-autogen-id-17&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.leaf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;(The shadowRoot has its own query selector function separate from the window of the console.)&lt;/p&gt;

&lt;p&gt;Then, I right clicked the result to paste it into a text editor and save as a json file&lt;/p&gt;

&lt;p&gt;Here's a sample of what you can expect to get:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4hi4f6zbz878l5ylu426.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4hi4f6zbz878l5ylu426.png" alt="Chrome reading list as json"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The following is one particular chrome reading list item&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"CLIENT_TAG_HASH"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xxxxbEkAs="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"CTIME"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2022-07-25 14:41:44 +03"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sZ:ADqtAZw/jKp+WHaGmzqqSHy/YRNDZvdUo0doOqiQFaIMf0Yo59vheTu0WKfrimEtw+TaNyZJxSpEzC1wpOhS6r5qtfZP1kywYw=="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"MTIME"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2022-07-25 14:41:44 +03"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"NAME"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://developer.mozilla.org/en-US/docs/Web/API/Reporting_API"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"NON_UNIQUE_NAME"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://developer.mozilla.org/en-US/docs/Web/API/Reporting_API"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ORIGINATOR_CACHE_GUID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ORIGINATOR_CLIENT_ITEM_ID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"PARENT_ID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"SERVER_DEFINED_UNIQUE_TAG"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"SPECIFICS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"reading_list"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"creation_time_us"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1658749304297842"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"entry_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://developer.mozilla.org/en-US/docs/Web/API/Reporting_API"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"estimated_read_time_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"first_read_time_us"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UNREAD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Reporting API - Web APIs | MDN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"update_time_us"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1658749304297842"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"update_title_time_us"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1658749304297842"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://developer.mozilla.org/en-US/docs/Web/API/Reporting_API"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"metadata"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"acked_sequence_number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"client_tag_hash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3ewNy9zDJ7HvgpPTJfkN1DbEkAs="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"creation_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1658749304297"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"is_deleted"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"modification_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1658749304299"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"sequence_number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"server_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Z:ADqtAZw/jKp+WHaGmzqqSHy/YRNDZvdUo0doOqiQFaIMf0Yo59vheTu0WKfrimEtw+TaNyZJxSpEzC1wpOhS6r5qtfZP1kywYw=="&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"server_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1658749304544398"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"specifics_hash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eb+IzTHvIE0GMJ0az8W4a1nUlis="&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"modelType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Reading List"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>javascript</category>
    </item>
    <item>
      <title>Benchmark of inter-thread communication options in a concurrent Python GUI</title>
      <dc:creator>Michael Salaverry</dc:creator>
      <pubDate>Wed, 24 May 2023 11:32:39 +0000</pubDate>
      <link>https://dev.to/barakplasma/benchmark-of-inter-thread-communication-options-in-a-concurrent-python-gui-2e7e</link>
      <guid>https://dev.to/barakplasma/benchmark-of-inter-thread-communication-options-in-a-concurrent-python-gui-2e7e</guid>
      <description>&lt;p&gt;In this blog post, we'll discuss a benchmarking process I used while refactoring the software for a microscope. The experiment compares the performance of two methods for sending and receiving images between a tkinter frontend and a python backend controlling the microscope: using the blinker package and a Flask-based web server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;We have a custom built microscope at the startup I work at which has a custom built tkinter GUI for controlling it. The architecture when I got there was that the tkinter gui would communicate with the microscope control module via flask / http calls. The idea was to allow future remote control of the microscope from other programs. Additionally, the microscope images needed to be displayed in the UI in real time for focusing the microscope, and verifying the images looked good before saving them to disk. This was previously happening via a python queue which contained PIL images.&lt;/p&gt;

&lt;p&gt;There were issues sharing the queue across the various modules. Each module needed to be aware of queue full/empty logic. Each module needed to get a reference to the queue, which was owned by the flask server module. We needed to display the most recent image even if the queue was full of old images that hadn't been displayed yet. The queue was in memory, and the images were each quite large, so there were concerns around memory / RAM capacity if the queue filled up.&lt;/p&gt;

&lt;p&gt;I decided to benchmark an alternative I thought of: using the blinker library as an event emitter architecture rather than queues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;Both version of inter-thread communication will check the received image at the end to mimic the save to disk process. And both share the same scan function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;checker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;microscope_scan&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'./example_thorlabs.png'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the benchmark, I start the flask uWSGI server below with &lt;code&gt;waitress-serve --host 127.0.0.1 --port 5000 flask_server:app&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;io&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BytesIO&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;send_file&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;scan&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;microscope_scan&lt;/span&gt;

&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/get_latest_image"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_latest_image&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;bytes_io&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BytesIO&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;microscope_scan&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes_io&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'PNG'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;bytes_io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;send_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes_io&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mimetype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'image/png'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The following is the essence of the previous flask version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;io&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BytesIO&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;concurrent.futures&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ThreadPoolExecutor&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;Pool&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cpu_count&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;flasker&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;Pool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cpu_count&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flask_receiver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repititions&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;flask_receiver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'http://localhost:5000/get_latest_image'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BytesIO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;checker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This get's the latest image from the backend over http calls and checks the size of the received image.&lt;/p&gt;

&lt;p&gt;The following is the blinker version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;blinker&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_via_blink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;scanned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'scanned'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;scanned&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'microscope'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;blinker&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;96&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;send_via_blink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;microscope_scan&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="n"&gt;scanned&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'scanned'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;scanned&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;blink_receiver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;checker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'image'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also added a queue version of the benchmark:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;############# Queue #############
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;queue&lt;/span&gt;

&lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;queueer&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repititions&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;microscope_scan&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;queue_receiver&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;checker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Benchmarking
&lt;/h3&gt;

&lt;p&gt;Finally, I used the timeit module to benchmark the performance of the blinker and flasker functions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;timeit&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timeit&lt;/span&gt;
&lt;span class="n"&gt;times&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"queueer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"queueer()"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"from __main__ import queueer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"blinker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"blinker()"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"from __main__ import blinker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"flasker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"flasker()"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"from __main__ import flasker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"| Method | Time (seconds) |"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"| ------ | ------ |"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"| &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; | &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; |"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Based on the results from the timeit benchmark, we can see a significant difference in the time taken by both methods:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Time (seconds)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;queueer&lt;/td&gt;
&lt;td&gt;0.1009&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;blinker&lt;/td&gt;
&lt;td&gt;0.0301&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flasker&lt;/td&gt;
&lt;td&gt;1573.2531&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;async_flasker&lt;/td&gt;
&lt;td&gt;1618.7829&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Blinker method is much faster compared to the Flask method. This is likely because Flask introduces additional overhead due to HTTP requests and responses, while Blinker is a lightweight library designed for in-process communication using signals.&lt;/p&gt;

&lt;p&gt;Given the substantial difference in performance, it would be advisable to use the Blinker method for sending and receiving images in the refactored microscope software. This will help to ensure faster processing times and a more responsive system. However, it's important to consider other factors such as ease of implementation, maintainability, and any other specific requirements for your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This code demonstrates a benchmark experiment comparing the performance of sending and receiving images using the blinker library and a Flask-based web server. By analyzing the results, we can make informed decisions on which approach to use for the refactored microscope software.&lt;/p&gt;

&lt;p&gt;The code in executable form is available at &lt;a href="https://gist.github.com/barakplasma/ea7ed66109a14d597c24e2374ed975ad"&gt;https://gist.github.com/barakplasma/ea7ed66109a14d597c24e2374ed975ad&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>performance</category>
    </item>
    <item>
      <title>How to send syslog logs from docker containers to a nearby Synology NAS</title>
      <dc:creator>Michael Salaverry</dc:creator>
      <pubDate>Sun, 26 Mar 2023 12:53:04 +0000</pubDate>
      <link>https://dev.to/barakplasma/how-to-send-syslog-logs-from-docker-containers-to-a-nearby-synology-nas-338n</link>
      <guid>https://dev.to/barakplasma/how-to-send-syslog-logs-from-docker-containers-to-a-nearby-synology-nas-338n</guid>
      <description>&lt;p&gt;Using this docker compose yaml:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.9'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;log-generator-syslog&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mingrammer/flog&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flog -f rfc3164 -n &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;
    &lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;syslog&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;syslog-address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;udp://nas.local:514"&lt;/span&gt;
        &lt;span class="na"&gt;syslog-format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rfc3164&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev&lt;/span&gt;
        &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tlv,michael-laptop&lt;/span&gt;
        &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{.Name}}-{{.ID}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can generate fake logs using &lt;a href="https://hub.docker.com/r/mingrammer/flog" rel="noopener noreferrer"&gt;https://hub.docker.com/r/mingrammer/flog&lt;/a&gt; in the BSD syslog format. These logs are sent via UDP to the NAS in the same network&lt;/p&gt;

&lt;p&gt;On the receiving NAS end, open the Log Center, and setup an endpoint for the container to write to.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjny68mqwrp4c1kx2yn0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsjny68mqwrp4c1kx2yn0.png" alt="config on synology nas for syslog"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, just run the docker compose file with &lt;code&gt;docker compose up&lt;/code&gt; and you should see your fake logs in synology! These logs can be searched and filtered&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F566zilx701k5ry0o5pbz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F566zilx701k5ry0o5pbz.png" alt="fake logs"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
    </item>
    <item>
      <title>ChatGPT can draw system architecture diagrams using mermaid</title>
      <dc:creator>Michael Salaverry</dc:creator>
      <pubDate>Sun, 19 Mar 2023 13:42:39 +0000</pubDate>
      <link>https://dev.to/barakplasma/chatgpt-can-draw-system-architecture-diagrams-using-mermaid-34p6</link>
      <guid>https://dev.to/barakplasma/chatgpt-can-draw-system-architecture-diagrams-using-mermaid-34p6</guid>
      <description>&lt;p&gt;Turns out that ChatGPT can draw system architecture diagrams for it's suggestions using mermaid.js&lt;/p&gt;

&lt;p&gt;I asked ChatGPT for some system architecture advice for monitoring some windows and linux on-prem servers my company runs using this prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;... (conversation including details on my goals)&lt;br&gt;
draw a system architecture design using mermaid and markdown&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is the design ChatGPT produced:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

graph LR
    A[VPN] --&amp;gt; B[On-Prem Windows]
    A --&amp;gt; C[On-Prem Linux]
    A --&amp;gt; D[Docker Containers]
    B --&amp;gt; E{Prometheus}
    C --&amp;gt; E
    D --&amp;gt; E
    E --&amp;gt; F[Grafana]
    G[User] --&amp;gt; F


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fumbiklmedq0m2nbb9b65.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fumbiklmedq0m2nbb9b65.png" alt="system architecture diagram by chatgpt"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>markdown</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Vue 3 Mouse and Keyboard event combo</title>
      <dc:creator>Michael Salaverry</dc:creator>
      <pubDate>Wed, 12 Oct 2022 09:05:06 +0000</pubDate>
      <link>https://dev.to/barakplasma/vue-3-mouse-and-keyboard-event-combo-3c17</link>
      <guid>https://dev.to/barakplasma/vue-3-mouse-and-keyboard-event-combo-3c17</guid>
      <description>&lt;p&gt;I had trouble figuring out keyboard events in Vue, so I'm writing this for future reference&lt;/p&gt;

&lt;p&gt;&lt;a href="https://sfc.vuejs.org/#eNp1U8GOmzAQ/ZURF4iUQKveKInaSpV66K3H0gPgYSELtmUPpBHi3zvGZnfTqr7Y4/f8PPPsWaLPWqfzhFEeFbYxvSawSJO+lBJ49KNWhmABgy2s0Bo1Qsz0uJSe0ChpCTo1o4GzYyVtNVg8vEWf8R6wOH4AcEZJNmA/fzHmUaGaaWQorYT46jjfe0so0SQxawl1k/EREjzA+QKLP+IGY+lcDROyIqYcfXzF/FUeTvVku2Rx/NwTj76C3E+BReoHmV4+JQdYQ9puLjLvEzvEAeGoh4ow+FV0H8IK4NtmSqIMSEUHoA6hnoiUhBoHdYNKCqC7xnAyezlaBNqnUU0Wncq5jHaLyUxYRgEbsJrxDbh5z2gzVNbu+19IltFLViGvHJZlQ9d1v9/fuudAVT3sVfmN7sJOFRnPj7tici556/6BDcynVrkCcIZehnd4yMcTxWVZcHaPsa6sIv5HCEn/ReHQ7Jlne+q8en0dDi3dQ0lehY3Zv0+tjHCmvNe/waqhF1APVfMcPtCtF9Qx+M7guG2xZ/wNNrnoGPkmOY2VTq9WSW6lTbUMAFfLbnulMuLmcTG/DZG2eZbZtnENeLWpMk8Zr1IzSepHTNGOp9qom+UveWWVcPMarX8Ab/IwVw=="&gt;Edit in Vue SFC Playground&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


</description>
      <category>vue</category>
    </item>
    <item>
      <title>data manipulation: jq vs Miller</title>
      <dc:creator>Michael Salaverry</dc:creator>
      <pubDate>Mon, 24 Jan 2022 14:52:40 +0000</pubDate>
      <link>https://dev.to/barakplasma/data-manipulation-jq-vs-miller-4g89</link>
      <guid>https://dev.to/barakplasma/data-manipulation-jq-vs-miller-4g89</guid>
      <description>&lt;p&gt;Recently, I was confronted with a 700mb json file which I needed to manipulate and explore in order to find the source of a bug. I needed to find any relationships between an occasionally missing field, and the other properties. Finding a pattern would let me focus my debugging efforts.&lt;/p&gt;

&lt;p&gt;My first attempt was actually a simple Node.js script which would fs.readFile and use vanilla JS to map/reduce/summarize the JSON. This was a good place to start, as it was fairly easy to launch the Node REPL and to &lt;code&gt;.save&lt;/code&gt; and &lt;code&gt;.load&lt;/code&gt; as needed between sessions. However, I didn't like the wait associated with the single threaded parsing of the JSON file. While it was the most flexible option, it felt the most manual.&lt;/p&gt;

&lt;p&gt;My usual tool for dealing with JSON, &lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/stedolan"&gt;
        stedolan
      &lt;/a&gt; / &lt;a href="https://github.com/stedolan/jq"&gt;
        jq
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Command-line JSON processor
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
jq&lt;/h1&gt;
&lt;p&gt;jq is a lightweight and flexible command-line JSON processor.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://coveralls.io/github/stedolan/jq?branch=master" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/ae40f15cea1010e9c00d1c408982b9c92273ec9d7a93b3a51482afec28030ae9/68747470733a2f2f636f766572616c6c732e696f2f7265706f732f737465646f6c616e2f6a712f62616467652e7376673f6272616e63683d6d617374657226736572766963653d676974687562" alt="Coverage Status"&gt;&lt;/a&gt;
Unix: &lt;a href="https://travis-ci.org/stedolan/jq" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/612cb24a46939804ae9217d429fb6b1ae5c1f85848975b071de4af7915ced258/68747470733a2f2f7472617669732d63692e6f72672f737465646f6c616e2f6a712e7376673f6272616e63683d6d6173746572" alt="Build Status"&gt;&lt;/a&gt;
Windows: &lt;a href="https://ci.appveyor.com/project/stedolan/jq" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/faa0a7fb4771ec126ca096db82f9ef3d864493d57455b3f3475d02d1be3dd07b/68747470733a2f2f63692e6170707665796f722e636f6d2f6170692f70726f6a656374732f7374617475732f6d69383136383131633965396d7832393f7376673d74727565" alt="Windows build status"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you want to learn to use jq, read the documentation at
&lt;a href="https://stedolan.github.io/jq" rel="nofollow"&gt;https://stedolan.github.io/jq&lt;/a&gt;.  This
documentation is generated from the docs/ folder of this repository
You can also try it online at &lt;a href="https://jqplay.org" rel="nofollow"&gt;jqplay.org&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you want to hack on jq, feel free, but be warned that its internals
are not well-documented at the moment. Bring a hard hat and a
shovel.  Also, read the wiki: &lt;a href="https://github.com/stedolan/jq/wiki"&gt;https://github.com/stedolan/jq/wiki&lt;/a&gt;, where
you will find cookbooks, discussion of advanced topics, internals,
release engineering, and more.&lt;/p&gt;
&lt;p&gt;Source tarball and built executable releases can be found on the
homepage and on the github release page, &lt;a href="https://github.com/stedolan/jq/releases"&gt;https://github.com/stedolan/jq/releases&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you're building directly from the latest git, you'll need flex
bison (3.0 or newer), libtool, make, automake, and autoconf installed.
To get regexp support you'll also need to install Oniguruma or clone it as…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/stedolan/jq"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
 is a workhouse written in C which has a variety of useful verbs for parsing and summarizing JSONs with one liners.&lt;br&gt;
In this case, jq is fast, but still single threaded.

&lt;p&gt;Instead, I turned to a relatively newer tool &lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/johnkerl"&gt;
        johnkerl
      &lt;/a&gt; / &lt;a href="https://github.com/johnkerl/miller"&gt;
        miller
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Miller is like awk, sed, cut, join, and sort for name-indexed data such as CSV, TSV, and tabular JSON
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
What is Miller?&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Miller is like awk, sed, cut, join, and sort for data formats such as CSV, TSV, JSON, JSON Lines, and positionally-indexed.&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;
What can Miller do for me?&lt;/h1&gt;
&lt;p&gt;With Miller, you get to use named fields without needing to count positional
indices, using familiar formats such as CSV, TSV, JSON, JSON Lines, and
positionally-indexed.  Then, on the fly, you can add new fields which are
functions of existing fields, drop fields, sort, aggregate statistically
pretty-print, and more.&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/johnkerl/miller./docs/src/coverart/cover-combined.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ustVl5hi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/johnkerl/miller./docs/src/coverart/cover-combined.png" alt="cover-art"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Miller operates on &lt;strong&gt;key-value-pair data&lt;/strong&gt; while the familiar
Unix tools operate on integer-indexed fields: if the natural data structure for
the latter is the array, then Miller's natural data structure is the
insertion-ordered hash map.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Miller handles a &lt;strong&gt;variety of data formats&lt;/strong&gt;
including but not limited to the familiar &lt;strong&gt;CSV&lt;/strong&gt;, &lt;strong&gt;TSV&lt;/strong&gt;, and &lt;strong&gt;JSON&lt;/strong&gt;/&lt;strong&gt;JSON Lines&lt;/strong&gt;
(Miller can handle &lt;strong&gt;positionally-indexed data&lt;/strong&gt; too!)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the above image you…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/johnkerl/miller"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
 which serves a similar purpose to jq. When I tried miller, I noticed that it's count-distinct verb was quite fast. I read the Miller documentation, and discovered on &lt;a href="https://miller.readthedocs.io/en/latest/cpu/"&gt;https://miller.readthedocs.io/en/latest/cpu/&lt;/a&gt; that Miller is often multi-threaded as a consequence of it's implementation in Go and use of channels. This can give Miller the edge over jq for certain tasks where concurrency can make a difference.

&lt;p&gt;I plan on diving deeper into Miller, especially since it can handle data in CSV, JSON, or other formats. Also, the then-chaining launching separate goroutines sounds like a big win for more complicated data manipulation flows.&lt;/p&gt;

&lt;p&gt;Another tool I tried using is sqlite and it's json extension combined with it's readfile extension. This was inspired by this question / answer I found &lt;/p&gt;
&lt;div class="ltag__stackexchange--container"&gt;
  &lt;div class="ltag__stackexchange--title-container"&gt;
    
      &lt;div class="ltag__stackexchange--title"&gt;
        &lt;div class="ltag__stackexchange--header"&gt;
          &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7Gn-iPj_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/stackoverflow-logo-b42691ae545e4810b105ee957979a853a696085e67e43ee14c5699cf3e890fb4.svg" alt=""&gt;
          &lt;a href="https://stackoverflow.com/questions/46407770/how-to-convert-a-json-file-to-an-sqlite-database/62606697#62606697" rel="noopener noreferrer"&gt;
            &lt;span class="title-flare"&gt;answer&lt;/span&gt; re: How to convert a JSON file to an SQLite database
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="ltag__stackexchange--post-metadata"&gt;
          &lt;span&gt;Jun 27 '20&lt;/span&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;a class="ltag__stackexchange--score-container" href="https://stackoverflow.com/questions/46407770/how-to-convert-a-json-file-to-an-sqlite-database/62606697#62606697" rel="noopener noreferrer"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y9mJpuJP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/stackexchange-arrow-up-eff2e2849e67d156181d258e38802c0b57fa011f74164a7f97675ca3b6ab756b.svg" alt=""&gt;
        &lt;div class="ltag__stackexchange--score-number"&gt;
          33
        &lt;/div&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wif5Zq3z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/stackexchange-arrow-down-4349fac0dd932d284fab7e4dd9846f19a3710558efde0d2dfd05897f3eeb9aba.svg" alt=""&gt;
      &lt;/a&gt;
    
  &lt;/div&gt;
  &lt;div class="ltag__stackexchange--body"&gt;
    
&lt;p&gt;&lt;strong&gt;A way do this without CSV or a 3rd party tool&lt;/strong&gt; is to use the &lt;a href="https://www.sqlite.org/json1.html" rel="noreferrer"&gt;&lt;code&gt;JSON1&lt;/code&gt; extension&lt;/a&gt; of SQLite combined with the &lt;a href="https://sqlite.org/cli.html#file_i_o_functions" rel="noreferrer"&gt;&lt;code&gt;readfile&lt;/code&gt; extension&lt;/a&gt; that is provided in the &lt;code&gt;sqlite3&lt;/code&gt; CLI tool. As well as overall being a "more direct" solution, this has the advantage of handling JSON NULL values…&lt;/p&gt;
    
  &lt;/div&gt;
  &lt;div class="ltag__stackexchange--btn--container"&gt;
    &lt;a href="https://stackoverflow.com/questions/46407770/how-to-convert-a-json-file-to-an-sqlite-database/62606697#62606697" class="ltag__stackexchange--btn" rel="noopener noreferrer"&gt;Open Full Answer&lt;/a&gt;
  &lt;/div&gt;
&lt;/div&gt;
 and ended up being a fast way to run sql on my JSON file. By running an sql statement in the sqlite3 REPL like this&lt;br&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;my_table&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; 
  &lt;span class="n"&gt;json_extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'$.user_agent'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;json_each&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;readfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'my_data.json'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I was able to create an SQL table based on the JSON keys I was interested in from each line. However, it wasn't a great fit for my data, as many lines are missing common keys, and I didn't want sparse rows. Also, I would have to write my own verbs in SQL, whereas Miller provides many verbs out of the box. However, sqlite3 was quite fast to provide an answer, probably as a result of it's query optimizations.&lt;/p&gt;

&lt;p&gt;I was interested in trying &lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/duckdb"&gt;
        duckdb
      &lt;/a&gt; / &lt;a href="https://github.com/duckdb/duckdb"&gt;
        duckdb
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      DuckDB is an in-process SQL OLAP Database Management System
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/ca392c8c5a86b5a9b35784daf8c4824db0fef67d43d980293d0ebd5b5a944e70/68747470733a2f2f6475636b64622e6f72672f696d616765732f4475636b44425f4c6f676f5f646c2e706e67"&gt;&lt;img src="https://camo.githubusercontent.com/ca392c8c5a86b5a9b35784daf8c4824db0fef67d43d980293d0ebd5b5a944e70/68747470733a2f2f6475636b64622e6f72672f696d616765732f4475636b44425f4c6f676f5f646c2e706e67" height="50"&gt;&lt;/a&gt;
&lt;/div&gt;

&lt;p&gt; &lt;/p&gt;

&lt;p&gt;
  &lt;a href="https://github.com/duckdb/duckdb/actions"&gt;
    &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BZbouo_j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/duckdb/duckdb/actions/workflows/Main.yml/badge.svg%3Fbranch%3Dmaster" alt="Github Actions Badge"&gt;
  &lt;/a&gt;
  &lt;a href="https://app.codecov.io/gh/duckdb/duckdb" rel="nofollow"&gt;
    &lt;img src="https://camo.githubusercontent.com/9a4ba6ce6ece2dc5d03887b4e6dcfbb8bcebbb31112abd213a70a9c5857f5e52/68747470733a2f2f636f6465636f762e696f2f67682f6475636b64622f6475636b64622f6272616e63682f6d61737465722f67726170682f62616467652e7376673f746f6b656e3d4661786a63664667684e" alt="codecov"&gt;
  &lt;/a&gt;
  &lt;a href="https://discord.gg/tcvwpjfnZx" rel="nofollow"&gt;
    &lt;img src="https://camo.githubusercontent.com/13308e9a886f90fabd681bfdec1d9c9a6575311ebd01510dbefb9c851a9e3d1a/68747470733a2f2f736869656c64732e696f2f646973636f72642f393039363734343931333039383530363735" alt="discord"&gt;
  &lt;/a&gt;
  &lt;a href="https://github.com/duckdb/duckdb/releases/"&gt;
    &lt;img src="https://camo.githubusercontent.com/186cf4a505d2e7646f2b7397dc01e9b906d562a068d1f9f72f490d7392fac305/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f6475636b64622f6475636b64623f636f6c6f723d627269676874677265656e26646973706c61795f6e616d653d746167266c6f676f3d6475636b6462266c6f676f436f6c6f723d7768697465" alt="Latest Release"&gt;
  &lt;/a&gt;
&lt;/p&gt;

&lt;h2&gt;
DuckDB&lt;/h2&gt;

&lt;p&gt;DuckDB is a high-performance analytical database system. It is designed to be fast, reliable and easy to use. DuckDB provides a rich SQL dialect, with support far beyond basic SQL. DuckDB supports arbitrary and nested correlated subqueries, window functions, collations, complex types (arrays, structs), and more. For more information on the goals of DuckDB, please refer to &lt;a href="https://duckdb.org/why_duckdb" rel="nofollow"&gt;the Why DuckDB page on our website&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
Installation&lt;/h2&gt;

&lt;p&gt;If you want to install and use DuckDB, please see &lt;a href="https://www.duckdb.org" rel="nofollow"&gt;our website&lt;/a&gt; for installation and usage instructions.&lt;/p&gt;

&lt;h2&gt;
Data Import&lt;/h2&gt;

&lt;p&gt;For CSV files and Parquet files, data import is as simple as referencing the file in the FROM clause:&lt;/p&gt;

&lt;div class="highlight highlight-source-sql notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;SELECT&lt;/span&gt; &lt;span class="pl-k"&gt;*&lt;/span&gt; &lt;span class="pl-k"&gt;FROM&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;myfile.csv&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;
&lt;span class="pl-k"&gt;SELECT&lt;/span&gt; &lt;span class="pl-k"&gt;*&lt;/span&gt; &lt;span class="pl-k"&gt;FROM&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;myfile.parquet&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Refer to our &lt;a href="https://duckdb.org/docs/data/overview" rel="nofollow"&gt;Data Import&lt;/a&gt; section for more information.&lt;/p&gt;
&lt;h2&gt;
SQL Reference&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://duckdb.org/docs/sql/introduction" rel="nofollow"&gt;website&lt;/a&gt; contains a reference of functions and SQL constructs available in DuckDB.&lt;/p&gt;
&lt;h2&gt;
Development&lt;/h2&gt;
&lt;p&gt;For development, DuckDB requires &lt;a href="https://cmake.org" rel="nofollow"&gt;CMake&lt;/a&gt;…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/duckdb/duckdb"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;for the same reason I tried sqlite3, but ultimately didn't pursue it since I had the json extension was easier than converting the json to csv first, and then importing to duckdb later.&lt;/p&gt;

</description>
      <category>productivity</category>
    </item>
    <item>
      <title>Append a file into a zip file in Python</title>
      <dc:creator>Michael Salaverry</dc:creator>
      <pubDate>Fri, 10 Dec 2021 09:27:14 +0000</pubDate>
      <link>https://dev.to/barakplasma/append-a-file-into-a-zip-file-in-python-3e0c</link>
      <guid>https://dev.to/barakplasma/append-a-file-into-a-zip-file-in-python-3e0c</guid>
      <description>&lt;p&gt;I was trying to find a way to append a file into a zip file in python, but I could not find an easy way to do it.&lt;/p&gt;

&lt;p&gt;When using zipfile built into python, using the 'a' append method doesn't overwrite files the way I expected it to. So this python module will overwrite the existing file when appending a file (which to me is the obvious solution).&lt;/p&gt;

&lt;p&gt;There's no lack of StackOverflow posts and answers, but all of those were too specific to the specific post/issue.&lt;/p&gt;

&lt;p&gt;So I decided to scratch my own itch, and make a library to handle it for myself and others.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pypi.org/project/appendzip/0.0.4/" rel="noopener noreferrer"&gt;https://pypi.org/project/appendzip/0.0.4/&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/barakplasma" rel="noopener noreferrer"&gt;
        barakplasma
      &lt;/a&gt; / &lt;a href="https://github.com/barakplasma/append-zip" rel="noopener noreferrer"&gt;
        append-zip
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      append a file into an existing zip file, overwriting the existing file of the same name if needed
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;append-zip&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;Appends a file to a zip file, overwriting the existing file there if necessary&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Performance&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Not efficient; extracts all the files in the zip, copies over the new file, and compresses a brand new zip replacing the original one. You will need enough disk space to duplicate the zip file.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Caveats&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;for some reason, windows has a different file length after unzipping (by 10-20 bytes). So beware how this works on windows&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Getting started&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;install (on Mac)
&lt;code&gt;$ python3 -m pip install appendzip&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight highlight-source-python notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s1"&gt;appendzip&lt;/span&gt;.&lt;span class="pl-s1"&gt;appendzip&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-s1"&gt;appendzip&lt;/span&gt;
&lt;span class="pl-c"&gt;# before appendzip calendar.txt in the zip archive test.zip contains 2021-01-02&lt;/span&gt;
&lt;span class="pl-c"&gt;# before appendzip calendar.txt outside the zip contains 2022-02-03&lt;/span&gt;
&lt;span class="pl-en"&gt;appendzip&lt;/span&gt;(
            &lt;span class="pl-s1"&gt;pathlib&lt;/span&gt;.&lt;span class="pl-v"&gt;Path&lt;/span&gt;(&lt;span class="pl-s"&gt;'test.zip'&lt;/span&gt;),
            &lt;span class="pl-s1"&gt;pathlib&lt;/span&gt;.&lt;span class="pl-v"&gt;Path&lt;/span&gt;(&lt;span class="pl-s"&gt;'calendar.txt'&lt;/span&gt;),
            &lt;span class="pl-s"&gt;'calendar.txt'&lt;/span&gt;
)
&lt;span class="pl-c"&gt;# after appendzip calendar.txt inside the zip contains 2022-02-03&lt;/span&gt;
&lt;span class="pl-c"&gt;# after, there is still only one file in the zip archive test.zip&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/barakplasma/append-zip" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;MIT licensed&lt;/p&gt;

&lt;p&gt;example of how to use it:&lt;/p&gt;

&lt;p&gt;install (on Mac)&lt;br&gt;
&lt;code&gt;$ python3 -m pip install appendzip&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;appendzip.appendzip&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;appendzip&lt;/span&gt;
&lt;span class="c1"&gt;# before appendzip calendar.txt in the zip archive test.zip contains 2021-01-02
# before appendzip calendar.txt outside the zip contains 2022-02-03
&lt;/span&gt;&lt;span class="nf"&gt;appendzip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;test.zip&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;calendar.txt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;calendar.txt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# after appendzip calendar.txt inside the zip contains 2022-02-03
# after, there is still only one file in the zip archive test.zip
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>python</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Hanukkah lighting in WebXR</title>
      <dc:creator>Michael Salaverry</dc:creator>
      <pubDate>Sun, 05 Dec 2021 09:21:18 +0000</pubDate>
      <link>https://dev.to/barakplasma/hanukkah-lighting-in-webxr-31m7</link>
      <guid>https://dev.to/barakplasma/hanukkah-lighting-in-webxr-31m7</guid>
      <description>&lt;p&gt;To celebrate Hanukkah (חנוכה), I made a WebXR scene where the user can pan, walk around (WASD), and/or enter VR in the Western Wall (הכותל המערבי) using AFrame, an HTML/JS framework for creating WebXR experiences.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/aframevr"&gt;
        aframevr
      &lt;/a&gt; / &lt;a href="https://github.com/aframevr/aframe"&gt;
        aframe
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🅰️ Web framework for building virtual reality experiences.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
A-Frame&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://aframe.io" rel="nofollow"&gt;&lt;img width="480" alt="A-Frame" src="https://res.cloudinary.com/practicaldev/image/fetch/s--SnjBYszj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/674727/32120889-230ef110-bb0f-11e7-908c-76e39aa43149.jpg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;A web framework for building virtual reality experiences.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;
  &lt;a href="https://codecov.io/gh/aframevr/aframe" rel="nofollow"&gt;
    &lt;img src="https://camo.githubusercontent.com/32dbf3c584b942b2f3b5888104d79cac717b372392fe47a03132309b717a9271/68747470733a2f2f636f6465636f762e696f2f67682f616672616d6576722f616672616d652f6272616e63682f6d61737465722f67726170682f62616467652e737667" alt="Coverage Status"&gt;
  &lt;/a&gt;
  &lt;a href="https://npmjs.org/package/aframe" rel="nofollow"&gt;
    &lt;img src="https://camo.githubusercontent.com/fcf3945f8c964ffee7f68191a010bf449fd7f87efa06356234d936585d94ae2e/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f64742f616672616d652e7376673f7374796c653d666c61742d737175617265" alt="Downloads"&gt;
  &lt;/a&gt;
  &lt;a href="https://npmjs.org/package/aframe" rel="nofollow"&gt;
    &lt;img src="https://camo.githubusercontent.com/8c83082c3ef3859b46b85a04202114426615d5a203b285f015e0dffbf088a52f/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f616672616d652e7376673f7374796c653d666c61742d737175617265" alt="Version"&gt;
  &lt;/a&gt;
  &lt;a href="https://npmjs.com/package/aframe" rel="nofollow"&gt;
    &lt;img src="https://camo.githubusercontent.com/d679132107f41b1a62b3baa813d45812e36c13ea8252a3fb8903c162859729a9/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f6c2f616672616d652e7376673f7374796c653d666c61742d737175617265" alt="License"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;div&gt;
  &lt;a href="https://aframe.io" rel="nofollow"&gt;Site&lt;/a&gt;
  —
  &lt;a href="https://aframe.io/docs/" rel="nofollow"&gt;Docs&lt;/a&gt;
  —
  &lt;a href="https://aframe.io/school/" rel="nofollow"&gt;School&lt;/a&gt;
  —
  &lt;a href="https://aframevr.slack.com/join/shared_invite/zt-f6rne3ly-ekVaBU~Xu~fsZHXr56jacQ" rel="nofollow"&gt;Slack&lt;/a&gt;
  —
  &lt;a href="https://aframe.io/blog/" rel="nofollow"&gt;Blog&lt;/a&gt;
  —
  &lt;a href="https://aframe.io/subscribe/" rel="nofollow"&gt;Newsletter&lt;/a&gt;
&lt;/div&gt;

&lt;h2&gt;
Examples&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://supermedium.com/supercraft" rel="nofollow"&gt;&lt;br&gt;
  &lt;img alt="Supercraft" src="https://res.cloudinary.com/practicaldev/image/fetch/s--MbNs1Sdy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://user-images.githubusercontent.com/674727/41085457-f5429566-69eb-11e8-92e5-3210e4c6c4a0.gif" height="190" width="32%"&gt;&lt;br&gt;
&lt;/a&gt;&lt;br&gt;
&lt;a href="https://aframe.io/a-painter/?url=https://ucarecdn.com/962b242b-87a9-422c-b730-febdc470f203/" rel="nofollow"&gt;&lt;br&gt;
  &lt;img alt="A-Painter" src="https://res.cloudinary.com/practicaldev/image/fetch/s--_MrAtEfq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cloud.githubusercontent.com/assets/674727/24531388/acfc3dda-156d-11e7-8563-5bd75252f70f.gif" height="190" width="32%"&gt;&lt;br&gt;
&lt;/a&gt;&lt;br&gt;
&lt;a href="https://supermedium.com" rel="nofollow"&gt;&lt;br&gt;
  &lt;img alt="Supermedium" src="https://res.cloudinary.com/practicaldev/image/fetch/s--q8YKW7BM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/674727/37294616-7212cd20-25d3-11e8-9e7f-c0c61074f1e0.png" height="190" width="32%"&gt;&lt;br&gt;
&lt;/a&gt;&lt;br&gt;
&lt;a href="https://aframe.io/a-blast/" rel="nofollow"&gt;&lt;br&gt;
  &lt;img alt="A-Blast" src="https://res.cloudinary.com/practicaldev/image/fetch/s--3ffz39tZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cloud.githubusercontent.com/assets/674727/24531440/0336e66e-156e-11e7-95c2-f2e6ebc0393d.gif" height="190" width="32%"&gt;&lt;br&gt;
&lt;/a&gt;&lt;br&gt;
&lt;a href="https://aframe.io/a-saturday-night/" rel="nofollow"&gt;&lt;br&gt;
  &lt;img alt="A-Saturday-Night" src="https://res.cloudinary.com/practicaldev/image/fetch/s--Z14PK7kl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cloud.githubusercontent.com/assets/674727/24531477/44272daa-156e-11e7-8ef9-d750ed430f3a.gif" height="190" width="32%"&gt;&lt;br&gt;
&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/googlecreativelab/webvr-musicalforest"&gt;&lt;br&gt;
  &lt;img alt="Musical Forest by @googlecreativelab" src="https://res.cloudinary.com/practicaldev/image/fetch/s--Xi6dVlib--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://cloud.githubusercontent.com/assets/674727/25109861/b8e9ec48-2394-11e7-8f2d-ea1cd9df69c8.gif" height="190" width="32%"&gt;&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Find more examples on &lt;a href="https://aframe.io" rel="nofollow"&gt;the homepage&lt;/a&gt;, &lt;a href="https://aframe.io/blog/" rel="nofollow"&gt;A Week of A-Frame&lt;/a&gt;, and &lt;a href="https://webvr.directory" rel="nofollow"&gt;WebVR Directory&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
Features&lt;/h2&gt;

&lt;p&gt;👓 &lt;strong&gt;Virtual Reality Made Simple&lt;/strong&gt;: A-Frame handles the 3D and WebVR
boilerplate required to get running across platforms including mobile, desktop, Vive, and Rift just by dropping in &lt;code&gt;&amp;lt;a-scene&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;❤️ &lt;strong&gt;Declarative HTML&lt;/strong&gt;: HTML is easy to read and copy-and-paste. Since
A-Frame can be used from HTML, A-Frame is accessible to everyone: web
developers, VR enthusiasts, educators, artists, makers, kids.&lt;/p&gt;
&lt;p&gt;🔌 &lt;strong&gt;Entity-Component Architecture&lt;/strong&gt;: A-Frame is a powerful
framework on top of three.js, providing a declarative, composable, reusable
entity-component structure for three.js. While A-Frame can be used from HTML
developers have unlimited access to JavaScript, DOM APIs, three.js, WebVR, and
WebGL.&lt;/p&gt;
&lt;p&gt;⚡ &lt;strong&gt;Performance&lt;/strong&gt;: A-Frame is a thin framework on…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/aframevr/aframe"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;To make a WebXR scene, I used the webcomponents proved by AFrame to create a scene with &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a 360 image as the sky&lt;/li&gt;
&lt;li&gt;a number of cylinders as candles&lt;/li&gt;
&lt;li&gt;a number of gltf models for flames and the hanukiah (special thanks to &lt;a href="https://skfb.ly/6VLIn"&gt;Santiago Shang&lt;/a&gt; for the CC licensed beautiful GLTF model)&lt;/li&gt;
&lt;li&gt;and some english text (doesn't support hebrew / RTL as far as I can tell)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the &lt;a href="https://hanukah-aframe.barakplasma.repl.co/"&gt;live demo&lt;/a&gt; (click the ▶️ to see it in your browser, desktop or mobile)&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__replit"&gt;
  &lt;iframe height="550px" src="https://repl.it/@barakplasma/Hanukah-AFrame?lite=true"&gt;&lt;/iframe&gt;
&lt;/div&gt;


&lt;p&gt;Future plans involve adding interactivity, so that using a Quest 2 controller / pointer lets you light each candle, or to integrate &lt;a href="https://github.com/hebcal/hebcal-es6"&gt;hebcal&lt;/a&gt; so that it can automatically light the right number of candles.&lt;/p&gt;

</description>
      <category>webxr</category>
      <category>html</category>
      <category>vr</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Add Notifications to a 3rd party Website</title>
      <dc:creator>Michael Salaverry</dc:creator>
      <pubDate>Wed, 17 Nov 2021 09:09:02 +0000</pubDate>
      <link>https://dev.to/barakplasma/add-notifications-to-a-3rd-party-website-4g1o</link>
      <guid>https://dev.to/barakplasma/add-notifications-to-a-3rd-party-website-4g1o</guid>
      <description>&lt;p&gt;Ever had to monitor a long running process on a webpage like Jenkins? Constantly switching tabs to check if a long running task is finished is a pain, and we can ameliorate it with a small amount of code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/notification"&gt;MDN has a great article on the notification API&lt;/a&gt; so I'll skip that part in this blog post. (Flavio Copes also wrote &lt;a href="https://flaviocopes.com/notifications-api/"&gt;a great introduction to the Notification API&lt;/a&gt;) What I want to show you is how to use that API in a page.&lt;/p&gt;

&lt;p&gt;There's a great tool called &lt;a href="https://violentmonkey.github.io/"&gt;Violent Monkey&lt;/a&gt; which lets you add your own Javascript userscripts to run on a 3rd party website. Using a userscript, we can trigger the Notification API for almost any page event. (There's also a cool separate tool for adding your own CSS called &lt;a href="https://add0n.com/stylus.html"&gt;stylus&lt;/a&gt; which is outside the scope of this blog).&lt;/p&gt;

&lt;p&gt;The trick is to add an event listener or selector in Javascript for the thing you want to notify on. For example, when Jenkins finishes a build, it changes the header color from blue to green or red. We can use a CSS selector via &lt;code&gt;document.querySelector()&lt;/code&gt; that when a classname has been added to a specific DOM node, that our script will trigger a notification.&lt;/p&gt;

&lt;p&gt;Check out my example using the selector method:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Notice that it runs in a somewhat recursive loop, with a base case that does not trigger a second alert.&lt;/p&gt;

&lt;p&gt;Which pages do you need a notification for?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>showdev: Serverless IoT Dashboard</title>
      <dc:creator>Michael Salaverry</dc:creator>
      <pubDate>Thu, 23 Sep 2021 13:48:30 +0000</pubDate>
      <link>https://dev.to/barakplasma/showdev-serverless-iot-dashboard-2h92</link>
      <guid>https://dev.to/barakplasma/showdev-serverless-iot-dashboard-2h92</guid>
      <description>&lt;p&gt;I want to showdev how I built a serverless IoT (internet-of-things) dashboard for a Tasmota / BME680 air quality sensor.&lt;/p&gt;

&lt;p&gt;Preview of the real-time dashboard:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc4atqvnm6ube7qgp54uu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc4atqvnm6ube7qgp54uu.png" alt="Dashboard Graph and data table"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Live Demo / Repository:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/barakplasma" rel="noopener noreferrer"&gt;
        barakplasma
      &lt;/a&gt; / &lt;a href="https://github.com/barakplasma/Browser-MQTT-Dashboard" rel="noopener noreferrer"&gt;
        Browser-MQTT-Dashboard
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      receives and stores MQTT messages relayed from a Tasmota ESP8266 and displays them on a realtime graph and table dashboard
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Browser-MQTT-Dashboard&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;receives and stores MQTT messages relayed from a Tasmota ESP8266 and displays them on a realtime graph and table dashboard&lt;/p&gt;
&lt;p&gt;Access it at&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://barakplasma.github.io/Browser-MQTT-Dashboard/" rel="nofollow noopener noreferrer"&gt;https://barakplasma.github.io/Browser-MQTT-Dashboard/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jsfiddle.net/gh/get/library/pure/barakplasma/Browser-MQTT-Dashboard/tree/master/Demo/" rel="nofollow noopener noreferrer"&gt;https://jsfiddle.net/gh/get/library/pure/barakplasma/Browser-MQTT-Dashboard/tree/master/Demo/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;or at &lt;a href="https://jsfiddle.net/barakplasma/4r5gzjkh/" rel="nofollow noopener noreferrer"&gt;https://jsfiddle.net/barakplasma/4r5gzjkh/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/barakplasma/Browser-MQTT-Dashboard" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;The hardware:&lt;br&gt;
Prototype:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F304qk1rx75021ny63rka.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F304qk1rx75021ny63rka.jpg" alt="ESP8266 + BME680 on a breadboard"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr2ag778tfm6kd5hrv9u0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr2ag778tfm6kd5hrv9u0.jpg" alt="ESP8266 + BME680 air quality sensor soldered together in a cardboard case"&gt;&lt;/a&gt;&lt;br&gt;
(I made the case out of corrugated cardboard I had lying around, and sealed it up with a few more sheets of cardboard glued together. I did leave vents so that it can detect the actual air too)&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://mermaid-js.github.io/mermaid-live-editor/edit/?gist=https://gist.github.com/barakplasma/2a7e257740750a658699da0effe44c9f" rel="noopener noreferrer"&gt;architecture&lt;/a&gt;:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F29r17g64ojgnrcigp1uq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F29r17g64ojgnrcigp1uq.png" alt="UML diagram of architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The data is collected using &lt;a href="https://tasmota.github.io/docs/" rel="noopener noreferrer"&gt;Tasmota&lt;/a&gt; on an ESP8266. I soldered a &lt;a href="https://www.bosch-sensortec.com/products/environmental-sensors/gas-sensors/bme680/" rel="noopener noreferrer"&gt;BME680&lt;/a&gt; directly to the ESP8266 pins and mapped the pins accordingly using the web config. I configured Tasmota  to send the data to a free public MQTT broker provided by &lt;a href="https://www.emqx.com/en/mqtt/public-mqtt5-broker" rel="noopener noreferrer"&gt;emqx&lt;/a&gt;. It would be ok to self host broker, but that wouldn't be as serverless. The Tasmota/ESP8266 is connected to my home wifi, and since it's only sending messages out, no special NAT changes or Port Forwarding was required.&lt;/p&gt;

&lt;p&gt;The serverless static site architecture is based on MQTT.js reading from an MQTT broker, and displaying the data using Chart.js with chartjs-plugin-streaming for real-time chart updates.&lt;br&gt;
I decided to use vanilla javascript DOM updates to keep it simple. The static site stores the messages within the browser for historical purposes, and to enable the first load to happen with live data. Static site hosting and CI/CD is done through Github (Pages and Actions).&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/mqttjs" rel="noopener noreferrer"&gt;
        mqttjs
      &lt;/a&gt; / &lt;a href="https://github.com/mqttjs/MQTT.js" rel="noopener noreferrer"&gt;
        MQTT.js
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      The MQTT client for Node.js and the browser
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;&lt;a rel="noopener noreferrer nofollow" href="https://raw.githubusercontent.com/mqttjs/MQTT.js/137ee0e3940c1f01049a30248c70f24dc6e6f829/MQTT.js.png"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmqttjs%2FMQTT.js%2F137ee0e3940c1f01049a30248c70f24dc6e6f829%2FMQTT.js.png" alt="mqtt.js"&gt;&lt;/a&gt;&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/mqttjs/MQTT.js/workflows/MQTT.js%20CI/badge.svg"&gt;&lt;img src="https://github.com/mqttjs/MQTT.js/workflows/MQTT.js%20CI/badge.svg" alt="Github Test Status"&gt;&lt;/a&gt; &lt;a href="https://codecov.io/gh/mqttjs/MQTT.js" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/05f7fc5d45bda5b7684992ef9def7e6309fb3194c09643e41c5f0211d645907f/68747470733a2f2f636f6465636f762e696f2f67682f6d7174746a732f4d5154542e6a732f6272616e63682f6d61737465722f67726170682f62616467652e737667" alt="codecov"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/mqttjs/MQTT.js/graphs/commit-activity" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/4ae374aff8abab3cb4e5774a4bd6094c3a9b01d52bb3ea6b838b37a39e5bd62f/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4d61696e7461696e65642533462d7965732d677265656e2e737667" alt="Maintenance"&gt;&lt;/a&gt;
&lt;a href="https://github.com/mqttjs/MQTT.js/pulls" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/d88d8d77fa79e828eea397f75a1ebd114d13488aeec4747477ffbd2274de95ed/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5052732d77656c636f6d652d627269676874677265656e2e737667" alt="PRs Welcome"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.npmjs.com/package/mqtt" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/48b3dcbb1bdec68f5587b58f71e7165fd5b624e01c8ba350369873da6e32cb56/68747470733a2f2f696d672e736869656c64732e696f2f6e6f64652f762f6d7174742e737667" alt="node"&gt; &lt;img src="https://camo.githubusercontent.com/b133c2aa426b98acd72f5aa52d309ba036a825616acf8994f1f2e115dbffe965/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f6d7174742e7376673f6c6f676f3d6e706d" alt="npm"&gt; &lt;img src="https://camo.githubusercontent.com/b045e1559fee619b942aee2cc65924c804babb1690c320dbf086eedc6db9690b/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f646d2f6d7174742e737667" alt="NPM Downloads"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;MQTT.js is a client library for the &lt;a href="http://mqtt.org/" rel="nofollow noopener noreferrer"&gt;MQTT&lt;/a&gt; protocol, written
in JavaScript for node.js and the browser.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Table of Contents&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/mqttjs/MQTT.js#notes" rel="noopener noreferrer"&gt;Upgrade notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mqttjs/MQTT.js#install" rel="noopener noreferrer"&gt;Installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mqttjs/MQTT.js#example" rel="noopener noreferrer"&gt;Example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mqttjs/MQTT.js#react-native" rel="noopener noreferrer"&gt;React Native&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mqttjs/MQTT.js#example" rel="noopener noreferrer"&gt;Import Styles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mqttjs/MQTT.js#cli" rel="noopener noreferrer"&gt;Command Line Tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mqttjs/MQTT.js#api" rel="noopener noreferrer"&gt;API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mqttjs/MQTT.js#browser" rel="noopener noreferrer"&gt;Browser&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mqttjs/MQTT.js#qos" rel="noopener noreferrer"&gt;About QoS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mqttjs/MQTT.js#typescript" rel="noopener noreferrer"&gt;TypeScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mqttjs/MQTT.js#weapp-alipay" rel="noopener noreferrer"&gt;Weapp and Ali support&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mqttjs/MQTT.js#contributing" rel="noopener noreferrer"&gt;Contributing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mqttjs/MQTT.js#sponsor" rel="noopener noreferrer"&gt;Sponsor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mqttjs/MQTT.js#license" rel="noopener noreferrer"&gt;License&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;MQTT.js is an OPEN Open Source Project, see the &lt;a href="https://github.com/mqttjs/MQTT.js#contributing" rel="noopener noreferrer"&gt;Contributing&lt;/a&gt; section to find out what this means.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/feross/standard" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/782d2d0db8e92fdc2177196d48147fe15de7413892a1c79ff4757f1b35234a09/68747470733a2f2f63646e2e7261776769742e636f6d2f6665726f73732f7374616e646172642f6d61737465722f62616467652e737667" alt="JavaScript Style Guide"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Important notes for existing users&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;v5.0.0&lt;/strong&gt; (07/2023)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Removes support for all end of life node versions (v12 and v14), and now supports node v18 and v20.&lt;/li&gt;
&lt;li&gt;Completely rewritten in Typescript 🚀.&lt;/li&gt;
&lt;li&gt;When creating &lt;code&gt;MqttClient&lt;/code&gt; instance &lt;code&gt;new&lt;/code&gt; is now required.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;v4.0.0&lt;/strong&gt; (Released 04/2020) removes support for all end of life node versions, and now supports node v12 and v14. It also adds improvements to
debug logging, along with some feature additions.&lt;/p&gt;
&lt;p&gt;As a &lt;strong&gt;breaking change&lt;/strong&gt;, by default a error handler is built into the MQTT.js client, so if any
errors…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/mqttjs/MQTT.js" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/chartjs" rel="noopener noreferrer"&gt;
        chartjs
      &lt;/a&gt; / &lt;a href="https://github.com/chartjs/Chart.js" rel="noopener noreferrer"&gt;
        Chart.js
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Simple HTML5 Charts using the &amp;lt;canvas&amp;gt; tag
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;
  &lt;a href="https://www.chartjs.org/" rel="nofollow noopener noreferrer"&gt;
    &lt;img src="https://camo.githubusercontent.com/55f9876b641ac2e14f04741350bb2138aaaebb4ef51642e2109f67188c5dacd9/68747470733a2f2f7777772e63686172746a732e6f72672f6d656469612f6c6f676f2d7469746c652e737667" alt="https://www.chartjs.org/"&gt;&lt;br&gt;
  &lt;/a&gt;
    Simple yet flexible JavaScript charting for designers &amp;amp; developers
&lt;/p&gt;

&lt;p&gt;
    &lt;a href="https://www.chartjs.org/docs/latest/getting-started/installation.html" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/fa1c5364e25cf3c212901ed9fc83841538f2f423c200cf19ab2841993df5a731/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f72656c656173652f63686172746a732f43686172742e6a732e7376673f7374796c653d666c61742d737175617265266d61784167653d363030" alt="Downloads"&gt;&lt;/a&gt;
    &lt;a href="https://github.com/chartjs/Chart.js/actions?query=workflow%3ACI+branch%3Amaster" rel="noopener noreferrer"&gt;&lt;img alt="GitHub Workflow Status" src="https://camo.githubusercontent.com/d193450f178dac7f5f2b12fb9041c2189ab0e4f9720731949e960d0bd7f4b97f/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f63686172746a732f43686172742e6a732f63692e796d6c3f6272616e63683d6d6173746572267374796c653d666c61742d737175617265"&gt;&lt;/a&gt;
    &lt;a href="https://coveralls.io/github/chartjs/Chart.js?branch=master" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/a0eaed85b6c47414faadff115531a974d89b3ed9bb1604b615296212c90028e9/68747470733a2f2f696d672e736869656c64732e696f2f636f766572616c6c732f63686172746a732f43686172742e6a732e7376673f7374796c653d666c61742d737175617265266d61784167653d363030" alt="Coverage"&gt;&lt;/a&gt;
    &lt;a href="https://github.com/chartjs/awesome" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/9f4534299c4fb07eccb37b82d3e7aa23cb225094b2dd2a311be7c4b9779c3ed8/68747470733a2f2f617765736f6d652e72652f62616467652d666c6174322e737667" alt="Awesome"&gt;&lt;/a&gt;
    &lt;a href="https://discord.gg/HxEguTK6av" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/ac7a0e2fce6d3f3cf8068347043f5371c9b824ecbebfdfe06d8dd65480f2b73a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f646973636f72642d63686172746a732d626c75653f7374796c653d666c61742d737175617265266d61784167653d33363030" alt="Discord"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Documentation&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;All the links point to the new version 4 of the lib.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.chartjs.org/docs/latest/" rel="nofollow noopener noreferrer"&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.chartjs.org/docs/latest/getting-started/index" rel="nofollow noopener noreferrer"&gt;Getting Started&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.chartjs.org/docs/latest/general/data-structures" rel="nofollow noopener noreferrer"&gt;General&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.chartjs.org/docs/latest/configuration/index" rel="nofollow noopener noreferrer"&gt;Configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.chartjs.org/docs/latest/charts/line" rel="nofollow noopener noreferrer"&gt;Charts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.chartjs.org/docs/latest/axes/index" rel="nofollow noopener noreferrer"&gt;Axes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.chartjs.org/docs/latest/developers/index" rel="nofollow noopener noreferrer"&gt;Developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/chartjs/awesome" rel="noopener noreferrer"&gt;Popular Extensions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.chartjs.org/samples/" rel="nofollow noopener noreferrer"&gt;Samples&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In case you are looking for an older version of the docs, you will have to specify the specific version in the url like this: &lt;a href="https://www.chartjs.org/docs/2.9.4/" rel="nofollow noopener noreferrer"&gt;https://www.chartjs.org/docs/2.9.4/&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Contributing&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;Instructions on building and testing Chart.js can be found in &lt;a href="https://www.chartjs.org/docs/master/developers/contributing.html#building-and-testing" rel="nofollow noopener noreferrer"&gt;the documentation&lt;/a&gt;. Before submitting an issue or a pull request, please take a moment to look over the &lt;a href="https://www.chartjs.org/docs/master/developers/contributing" rel="nofollow noopener noreferrer"&gt;contributing guidelines&lt;/a&gt; first. For support, please post questions on &lt;a href="https://stackoverflow.com/questions/tagged/chart.js" rel="nofollow noopener noreferrer"&gt;Stack Overflow&lt;/a&gt; with the &lt;code&gt;chart.js&lt;/code&gt; tag.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;License&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;Chart.js is available under the &lt;a href="https://github.com/chartjs/Chart.jsLICENSE.md" rel="noopener noreferrer"&gt;MIT license&lt;/a&gt;.&lt;/p&gt;

&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/chartjs/Chart.js" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;

&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nagix" rel="noopener noreferrer"&gt;
        nagix
      &lt;/a&gt; / &lt;a href="https://github.com/nagix/chartjs-plugin-streaming" rel="noopener noreferrer"&gt;
        chartjs-plugin-streaming
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Chart.js plugin for live streaming data
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/nagix/chartjs-plugin-streamingdocs/.vuepress/public/logo.svg"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fnagix%2Fchartjs-plugin-streamingdocs%2F.vuepress%2Fpublic%2Flogo.svg"&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;chartjs-plugin-streaming&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://npmjs.com/package/chartjs-plugin-streaming" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/d2ff4b38f8a5a2c0c0f03f9eba790e3b584e058a7fb2d42cb248b91980b564f2/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f63686172746a732d706c7567696e2d73747265616d696e672e7376673f7374796c653d666c61742d737175617265" alt="npm"&gt;&lt;/a&gt; &lt;a href="https://github.com/nagix/chartjs-plugin-streaming/actions?query=workflow%3ACI+branch%3Amaster" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/0aae3f91b984a69d53be32fd2cdebfed83de1d09ec22d522d8a33b2eba3592a1/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f776f726b666c6f772f7374617475732f6e616769782f63686172746a732d706c7567696e2d73747265616d696e672f43493f7374796c653d666c61742d737175617265" alt="GitHub Workflow Status"&gt;&lt;/a&gt; &lt;a href="https://codeclimate.com/github/nagix/chartjs-plugin-streaming" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/50f832e34f39ef9c1ef81b20a8f418f4a27deb423c5dd2d4a10ea1583fc61333/68747470733a2f2f696d672e736869656c64732e696f2f636f6465636c696d6174652f6d61696e7461696e6162696c6974792f6e616769782f63686172746a732d706c7567696e2d73747265616d696e672e7376673f7374796c653d666c61742d737175617265" alt="Code Climate"&gt;&lt;/a&gt; &lt;a href="https://github.com/chartjs/awesome" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/9f4534299c4fb07eccb37b82d3e7aa23cb225094b2dd2a311be7c4b9779c3ed8/68747470733a2f2f617765736f6d652e72652f62616467652d666c6174322e737667" alt="Awesome"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://www.chartjs.org" rel="nofollow noopener noreferrer"&gt;Chart.js&lt;/a&gt; plugin for live streaming data&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;chartjs-plugin-streaming 2.x requires Chart.js 3.0.0 or later. If you need Chart.js 2.x support, use the following versions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For Chart.js 2.9.x, 2.8.x or 2.7.x, use &lt;a href="https://github.com/nagix/chartjs-plugin-streaming/releases/tag/v1.9.0" rel="noopener noreferrer"&gt;version 1.9.0&lt;/a&gt; (&lt;a href="https://nagix.github.io/chartjs-plugin-streaming/1.9.0/" rel="nofollow noopener noreferrer"&gt;tutorials&lt;/a&gt; and &lt;a href="https://nagix.github.io/chartjs-plugin-streaming/1.9.0/samples/" rel="nofollow noopener noreferrer"&gt;samples&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;For Chart.js 2.6.x, use &lt;a href="https://github.com/nagix/chartjs-plugin-streaming/releases/tag/v1.2.0" rel="noopener noreferrer"&gt;version 1.2.0&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Documentation&lt;/h2&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nagix.github.io/chartjs-plugin-streaming/master/guide/" rel="nofollow noopener noreferrer"&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nagix.github.io/chartjs-plugin-streaming/master/guide/getting-started.html" rel="nofollow noopener noreferrer"&gt;Getting Started&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nagix.github.io/chartjs-plugin-streaming/master/guide/options.html" rel="nofollow noopener noreferrer"&gt;Options&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nagix.github.io/chartjs-plugin-streaming/master/guide/data-feed-models.html" rel="nofollow noopener noreferrer"&gt;Data Feed Models&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nagix.github.io/chartjs-plugin-streaming/master/guide/integration.html" rel="nofollow noopener noreferrer"&gt;Integration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nagix.github.io/chartjs-plugin-streaming/master/guide/performance.html" rel="nofollow noopener noreferrer"&gt;Performance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nagix.github.io/chartjs-plugin-streaming/master/guide/migration.html" rel="nofollow noopener noreferrer"&gt;Migration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nagix.github.io/chartjs-plugin-streaming/master/tutorials/" rel="nofollow noopener noreferrer"&gt;Tutorials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nagix.github.io/chartjs-plugin-streaming/master/samples/" rel="nofollow noopener noreferrer"&gt;Samples&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Development&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;You first need to install node dependencies (requires &lt;a href="https://nodejs.org/" rel="nofollow noopener noreferrer"&gt;Node.js&lt;/a&gt;):&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm install&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The following commands will then be available from the repository root:&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm run build      &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; build dist files&lt;/span&gt;
npm run build:dev  &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; build and watch for changes&lt;/span&gt;
npm run lint       &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; perform code linting&lt;/span&gt;
npm run package    &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; create an archive with dist files&lt;/span&gt;
npm run docs       &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; generate documentation (`dist/docs`)&lt;/span&gt;
npm run docs:dev   &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; generate documentation and watch for changes&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;License&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;chartjs-plugin-streaming is available under the &lt;a href="https://opensource.org/licenses/MIT" rel="nofollow noopener noreferrer"&gt;MIT license&lt;/a&gt;.&lt;/p&gt;

&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nagix/chartjs-plugin-streaming" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>showdev</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Splunk Alert on Percentage Change in text Field Frequency</title>
      <dc:creator>Michael Salaverry</dc:creator>
      <pubDate>Sun, 05 Sep 2021 17:17:07 +0000</pubDate>
      <link>https://dev.to/barakplasma/splunk-alert-on-percentage-change-in-text-field-frequency-3e4m</link>
      <guid>https://dev.to/barakplasma/splunk-alert-on-percentage-change-in-text-field-frequency-3e4m</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UBBmmsvH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yqyncxlenypoedjcvm82.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UBBmmsvH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yqyncxlenypoedjcvm82.jpg" alt="Image showing log analysis in splunk from https://www.splunk.com/en_us/devops/log-investigation.html"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.splunk.com/en_us/devops/log-investigation.html"&gt;Splunk&lt;/a&gt; is a tool for querying and summarizing unstructured or structured text. You can use Splunk to search, summarize, and alert on application logs.&lt;/p&gt;

&lt;p&gt;You can use Splunk to send alerts via email or Pagerduty when we notice a rise in certain error logs. It's important for us to monitor our error logs so that we know when our users are experiencing issues with our products. Errors are one of the "Golden Signals" for application health as described in the Google Site Reliability Engineering book online (&lt;a href="https://sre.google/sre-book/monitoring-distributed-systems/#:~:text=retrievals%20per%20second.-,Errors,-The%20rate%20of"&gt;link&lt;/a&gt;). &lt;/p&gt;

&lt;p&gt;Here's a hypothetical log that we need to alert on:&lt;br&gt;
&lt;code&gt;2021-09-05T16:35:18+00:0 level=ERROR logger=com.enterprise.payments PAY - reason=null payeeId=aceja payStatus=FAILED host=appDeployment-alfzf&lt;/code&gt;&lt;br&gt;
Which also has other logs, such as success logs&lt;br&gt;
&lt;code&gt;2021-09-05T13:35:18+00:0 level=INFO logger=com.enterprise.payments PAY - reason=accepted payeeId=baehg payStatus=SUCCESS host=appDeployment-alfzf&lt;/code&gt;&lt;br&gt;
Or in between logs&lt;br&gt;
&lt;code&gt;2021-09-05T17:35:18+00:0 level=WARN logger=com.enterprise.payments PAY - reason=decline payeeId=gaeaj payStatus=FAILED host=appDeployment-alfzf&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In the past, we setup our alerts to trigger when we get x number of error logs, where x is some number from 1 to 100 errors. This works most days, since usually we have steady traffic/usage.&lt;/p&gt;

&lt;p&gt;However, when our application gets high traffic, we can arrive at a triggered alert even when the system is operating "normally". The goal of our alerts and monitoring is to tell us when something unusual, or exceptional is happening; something we need to fix. Getting alerts that we have high traffic is nerve wracking, and wakes us up late at night for no reason. These false positives create pager burnout quickly.&lt;/p&gt;

&lt;p&gt;In order to avoid false alarms / false positives, I refactored one of our Splunk alerts from quantity-of-errors based, to percentage-of-requests-which-fail based.&lt;/p&gt;

&lt;p&gt;Here's an example of the query before:&lt;br&gt;
&lt;code&gt;index=example (PAY "FAILED" logger=com.enterprise.payments)&lt;/code&gt;&lt;br&gt;
which would alert when it finds greater than x results. This has the advantage of being simple, but also the downside of a false positive during high traffic. We don't know how many payments were successful, or just declined cards, not an error in our server flow, but rather in the user's cash flow.&lt;/p&gt;

&lt;p&gt;Here's an example of the query after making it alert on percentage of errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;index=example (PAY logger=com.enterprise.payments) 
| eval failureRate=if(match(payStatus, "FAILED"), 100, 0)
| timechart avg(failureRate) as percentFail 
| where percentFail &amp;gt; 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;By converting payStatus to a number, we can use avg later on &lt;code&gt;if(match(payStatus, "FAILED"), 100, 0)&lt;/code&gt;. This gives us a nice 0-100% timechart graph.&lt;/p&gt;

&lt;p&gt;Additionally, the &lt;code&gt;where percentFail &amp;gt; 5&lt;/code&gt; is critical, since it lets us set the alert to fire when it finds greater than 1 result. Using the where limits the number of results to results when the percentage of failures was high.&lt;/p&gt;

&lt;p&gt;Another side benefit is that we get the splunk error percentage directly in the PagerDuty alert, letting us know what to expect once we click out of PagerDuty and into Splunk.&lt;/p&gt;

&lt;p&gt;The final query powering our alert can also be tied to other relevant transaction logs by payeeId, avoid alerting if the volume of events is too low, and provide reasons for each log in the results table:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



</description>
      <category>splunk</category>
      <category>monitoring</category>
    </item>
  </channel>
</rss>
