<?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: Stephen Sadowski</title>
    <description>The latest articles on DEV Community by Stephen Sadowski (@sjsadowski).</description>
    <link>https://dev.to/sjsadowski</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%2F261648%2F3e7f0cd3-0a73-4f76-ae0e-4270d469049f.png</url>
      <title>DEV Community: Stephen Sadowski</title>
      <link>https://dev.to/sjsadowski</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sjsadowski"/>
    <language>en</language>
    <item>
      <title>Honeycomb, Python, and I: an OpenTelemetry Horror Story (With a Happy Ending)</title>
      <dc:creator>Stephen Sadowski</dc:creator>
      <pubDate>Mon, 18 Apr 2022 19:02:34 +0000</pubDate>
      <link>https://dev.to/sjsadowski/honeycomb-python-and-i-an-opentelemetry-horror-story-with-a-happy-ending-3hmc</link>
      <guid>https://dev.to/sjsadowski/honeycomb-python-and-i-an-opentelemetry-horror-story-with-a-happy-ending-3hmc</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;I'm currently on day 1.5 of my Staycation where I'm trying to catch up on my MBA coursework, but it can be somewhat painful and I needed a break. I decided to take up the challenge of finally integrating distributed tracing into my web apps.&lt;/p&gt;

&lt;p&gt;It's no surprise that my apps are mostly written using &lt;a href="https://sanic.dev"&gt;Sanic&lt;/a&gt; as I'm pretty involved with the project. I've been wanting to start testing &lt;a href="https://honeycomb.io"&gt;honeycomb&lt;/a&gt; out as well, so it seemed the perfect opportunity to try out.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenTelemetry &amp;amp; Python: The Nightmare Begins
&lt;/h2&gt;

&lt;p&gt;First thing I want to say is that the OpenTelemetry project is both admirable for its goals and awesome in its scope. There's a lot going for it.&lt;/p&gt;

&lt;p&gt;Second thing I want to say is that the documentation sucks. And I'm not the only person that thinks so. But it's okay enough to kind of work out the kinks once you've been spinning for a bit.&lt;/p&gt;

&lt;p&gt;The third thing I want to say is that there need to be better examples for both manual instrumentation and automatic instrumentation NOT using the Flask example. I love Flask, and it's definitely one of the most popular Python web frameworks, but if you search for "&lt;a href="https://dev.to/#"&gt;Python OpenTelemetry&lt;/a&gt;" that's pretty much all you get.&lt;/p&gt;

&lt;p&gt;For reference they all pretty much include the following instructions:&lt;/p&gt;

&lt;p&gt;In your shell&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virtualenv venv
&lt;span class="nb"&gt;source&lt;/span&gt; ./venv/bin/activate
pip &lt;span class="nb"&gt;install &lt;/span&gt;opentelemetry-api
pip &lt;span class="nb"&gt;install &lt;/span&gt;opentelemetry-sdk
pip &lt;span class="nb"&gt;install &lt;/span&gt;opentelemetry-distro
pip &lt;span class="nb"&gt;install &lt;/span&gt;opentelemetry-instrumentation-flask
pip &lt;span class="nb"&gt;install &lt;/span&gt;flask
pip &lt;span class="nb"&gt;install &lt;/span&gt;requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;app.py:&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;opentelemetry&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;opentelemetry.exporter.otlp.proto.grpc.trace_exporter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OTLPSpanExporter&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;opentelemetry.sdk.trace&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TracerProvider&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;opentelemetry.sdk.trace.export&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BatchSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ConsoleSpanExporter&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;opentelemetry.instrumentation.flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FlaskInstrumentor&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="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="n"&gt;FlaskInstrumentor&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;instrument_app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&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;"/"&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;hello_world&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;    &lt;span class="c1"&gt;# You can still use the OpenTelemetry API as usual to create custom spans    # within your trace    
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_as_current_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"do_work"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Hello, World!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is great... but I'm not using Flask and Sanic does not yet have &lt;a href="https://opentelemetry.io/docs/instrumentation/python/automatic/"&gt;automatic instrumentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honeycomb Tasting
&lt;/h2&gt;

&lt;p&gt;I've been a fan of the idea of &lt;a href="https://honeycomb.io"&gt;honeycomb&lt;/a&gt; for some time, and have interacted from time to time with the honeycomb team.&lt;/p&gt;

&lt;p&gt;I had signed up for a honeycomb account some time back, and thankfully they understand the concept of try-before-you-buy with a free tier that I never got to take advantage of. After logging in and setting up a new API key, I went to work attempting manual instrumentation of my sanic app.&lt;/p&gt;

&lt;p&gt;So first, let's just try exporting a span with a test script:&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;opentelemetry&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;opentelemetry.exporter.otlp.proto.grpc.trace_exporter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OTLPSpanExporter&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;opentelemetry.sdk.trace&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TracerProvider&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;opentelemetry.sdk.trace.export&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SimpleSpanProcessor&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;opentelemetry.sdk.resources&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Resource&lt;/span&gt;

&lt;span class="n"&gt;OTEL_EXPORTER_OTLP_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://api.honeycomb.io"&lt;/span&gt;
&lt;span class="n"&gt;OTEL_EXPORTER_OTLP_HEADERS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'x-honeycomb-team'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'no-i-wont-tell-you'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;OTEL_SERVICE_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"test-service-for-hcotel"&lt;/span&gt;

&lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'test'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TracerProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;processor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SimpleSpanProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OTLPSpanExporter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;OTEL_EXPORTER_OTLP_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;OTEL_EXPORTER_OTLP_HEADERS&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_span_processor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_tracer_provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tracer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_tracer&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="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_as_current_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"test-span"&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;"test service"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Failure, Failure, Failure
&lt;/h2&gt;

&lt;p&gt;My workflow often varies depending on location, time of day, phase of the moon, etc. My primary devices for development are a Lenovo X1 Nano running Fedora 35, a Dell XPS 8930 Desktop with some significant upgrades, an M1 Mac Mini, and a 14" MacBook Pro. &lt;/p&gt;

&lt;p&gt;When developing in Python, on the linux systems, things Just Work™ but M1 macs still have some quirks. During this test, I was working on the MacBook Pro&lt;/p&gt;

&lt;h3&gt;
  
  
  GRPC Problems
&lt;/h3&gt;

&lt;p&gt;On M1 macs, for some time now the &lt;a href="https://pypi.org/project/grpcio/"&gt;grpcio&lt;/a&gt; &lt;a href="https://peps.python.org/pep-0427/"&gt;wheel&lt;/a&gt; has been broken. There are a few different workarounds, but in some cases you won't even know you need them until you try testing out something that relies on it - say, the otel grpc trace exporter, and then you get a traceback like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Traceback &lt;span class="o"&gt;(&lt;/span&gt;most recent call last&lt;span class="o"&gt;)&lt;/span&gt;:
  File &lt;span class="s2"&gt;"/Users/ssadowski/projects/otel-honeycomb-test/test.py"&lt;/span&gt;, line 4, &lt;span class="k"&gt;in&lt;/span&gt; &amp;lt;module&amp;gt;
    from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
  File &lt;span class="s2"&gt;"/Users/ssadowski/projects/otel-honeycomb-test/__pypackages__/3.10/lib/opentelemetry/exporter/otlp/proto/grpc/trace_exporter/__init__.py"&lt;/span&gt;, line 20, &lt;span class="k"&gt;in&lt;/span&gt; &amp;lt;module&amp;gt;
    from grpc import ChannelCredentials, Compression
  File &lt;span class="s2"&gt;"/Users/ssadowski/projects/otel-honeycomb-test/__pypackages__/3.10/lib/grpc/__init__.py"&lt;/span&gt;, line 22, &lt;span class="k"&gt;in&lt;/span&gt; &amp;lt;module&amp;gt;
    from grpc import _compression
  File &lt;span class="s2"&gt;"/Users/ssadowski/projects/otel-honeycomb-test/__pypackages__/3.10/lib/grpc/_compression.py"&lt;/span&gt;, line 15, &lt;span class="k"&gt;in&lt;/span&gt; &amp;lt;module&amp;gt;
    from grpc._cython import cygrpc
ImportError: dlopen&lt;span class="o"&gt;(&lt;/span&gt;/Users/ssadowski/projects/otel-honeycomb-test/__pypackages__/3.10/lib/grpc/_cython/cygrpc.cpython-310-darwin.so, 0x0002&lt;span class="o"&gt;)&lt;/span&gt;: tried: &lt;span class="s1"&gt;'/Users/ssadowski/projects/otel-honeycomb-test/__pypackages__/3.10/lib/grpc/_cython/cygrpc.cpython-310-darwin.so'&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;mach-o file, but is an incompatible architecture &lt;span class="o"&gt;(&lt;/span&gt;have &lt;span class="s1"&gt;'x86_64'&lt;/span&gt;, need &lt;span class="s1"&gt;'arm64e'&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The solution for me was to do the following as part of my installation... sort of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PIP_NO_BINARY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;grpcio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gets me a bit farther, as now we get this weird error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;test &lt;/span&gt;service
Failed to &lt;span class="nb"&gt;export &lt;/span&gt;span batch, error code: StatusCode.UNAUTHENTICATED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Honeycomb's Bad Taste
&lt;/h3&gt;

&lt;p&gt;There are a few things missing in &lt;a href="https://docs.honeycomb.io/getting-data-in/opentelemetry/python/"&gt;honeycomb's documentation for using OpenTelemetry&lt;/a&gt; and unfortunately, that's not obvious. When reading through the docs, there are some significant misses that make honeycomb unusable as a collector, and what's worse if you're using the grpc exporter there are two major hurdles to surpass.&lt;/p&gt;

&lt;p&gt;I spent a huge amount of time - maybe 6 hours - digging into the problem. I was flipping back and forth between honeycomb's docs, the open telemetry docs, and the &lt;a href="https://github.com/open-telemetry/opentelemetry-python"&gt;open telemetry python repo&lt;/a&gt; digging through issues.&lt;/p&gt;

&lt;p&gt;One of the big problems is that the python exporters do this bit of stupid in the grpc exporter on lines 152-153. Also it's clear the author knew the issue, because they specifically disable the pylint warning!&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="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# pylint: disable=broad-except
&lt;/span&gt;                &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, we just catch the exception and don't really provide any detail, which yes, in our case just told us the error listed above.&lt;/p&gt;

&lt;p&gt;I kept digging, however, and found that honeycomb has experimental http support for open telemetry... I decided to give it a whirl and see if I could at least get a better error. Answer? Yes, and also answer too problem #1 with the honeycomb docs: the API endpoint was wrong. After switching to the http exporter, the error changed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;test &lt;/span&gt;service
Failed to &lt;span class="nb"&gt;export &lt;/span&gt;batch code: 400, reason: &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;:&lt;span class="s2"&gt;"invalid OTLP endpoint - should you be sending to /v1/traces?, see API docs https://docs.honeycomb.io/"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well gosh, probably I should be sending my traces there. So I updated my endpoint and switched back to grpc. Bad move.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;test &lt;/span&gt;service
Failed to &lt;span class="nb"&gt;export &lt;/span&gt;span batch, error code: StatusCode.UNAUTHENTICATED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well crap. Back to http? But first - is the http exporter really that much better?&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="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"Failed to export batch code: %s, reason: %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So yes, we'll get significantly more detail fromt he http exporter. But what did we get?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;test &lt;/span&gt;service
Failed to &lt;span class="nb"&gt;export &lt;/span&gt;batch code: 401, reason: &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"message"&lt;/span&gt;:&lt;span class="s2"&gt;"missing 'x-honeycomb-dataset' header"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now wait a minute, that's not even a header the &lt;a href="https://docs.honeycomb.io/getting-data-in/opentelemetry/python/#configure-and-run"&gt;doc says to use&lt;/a&gt;... so is that problem 2? If I add the header, what do I get from the http exporter this time?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;test &lt;/span&gt;service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Success? Yes! I was able to validate that the span was present, and that's what I needed. &lt;/p&gt;

&lt;p&gt;Making the changes to the grpc exporter got the same results, and so clearly that was the issue.&lt;/p&gt;

&lt;p&gt;In the end, I was able to start exporting spans to honeycomb and could take my learning with me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotta Go Fast
&lt;/h2&gt;

&lt;p&gt;So with no automatic instrumentation, how can we get things going quickly?&lt;/p&gt;

&lt;h3&gt;
  
  
  Off To The Races
&lt;/h3&gt;

&lt;p&gt;Like many python frameworks, Sanic has a method to hook into the request/response cycle. In Sanic, also like many python frameworks, we call this &lt;a href="https://sanic.dev/en/guide/basics/middleware.html#attaching-middleware"&gt;middleware&lt;/a&gt; and we can do some execution before and after. We also have some convenience decorators, which I generally don't use outside of doing some POCs. Because this is a POC, let's set up our hooks!&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="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;on_request&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;otel_req_middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;route_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&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="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s"&gt;'Error'&lt;/span&gt; &lt;span class="c1"&gt;# set a default route name
&lt;/span&gt;                                                                  &lt;span class="c1"&gt;# necessary because route isn't
&lt;/span&gt;                                                                  &lt;span class="c1"&gt;# set on some errors like NotFound
&lt;/span&gt;    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;route_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;         &lt;span class="c1"&gt;# create our span
&lt;/span&gt;    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"route"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;   &lt;span class="c1"&gt;# add our path as an event
&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;on_response&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;otel_rsp_middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;try&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;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# close the span, send to collector
&lt;/span&gt;    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&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="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                    &lt;span class="c1"&gt;# TODO: convert to logger, catch specific exceptions
&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;exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SanicException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;otel_exc_middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SanicException&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPResponse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ERROR&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# make sure to flag span as containing an error
&lt;/span&gt;    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"exception"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;traceback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extract_stack&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;()})&lt;/span&gt;      &lt;span class="c1"&gt;# Add an error event
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"status_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                      &lt;span class="c1"&gt;# return our error
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What's Happening Here?
&lt;/h3&gt;

&lt;p&gt;The request middleware executes before the main portion of the request, so in the&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;@app.on_request&lt;/code&gt;&lt;br&gt;
&lt;br&gt;
 function, I initialize the span and add an event that tells me either that it's a route or an error. The route will always be present unless a NotFound exception has bubbled up from the router because the route hasn't been defined.&lt;/p&gt;

&lt;p&gt;In the&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;@app.on_response&lt;/code&gt;&lt;br&gt;
&lt;br&gt;
 function, I close the span and in this situation, the span is exported to the collector.&lt;/p&gt;

&lt;p&gt;The exception function sets the span to contain an error, and add an event to contain the trace for the exception.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping It All Up
&lt;/h2&gt;

&lt;p&gt;At this point, spans are being exported to honeycomb for both successful requests and errors, so the Otel POC is largely complete. The big question is do I carry this further and add automatic instrumentation to Sanic, or do I complete the larger POC for the service first?&lt;/p&gt;

&lt;h3&gt;
  
  
  For Sanic
&lt;/h3&gt;

&lt;p&gt;The answer is that we're trying to help Sanic grow up, and it would be really helpful if we natively supported Open Telemetry either as an add-on package or as part of &lt;a href="https://sanic.dev/en/plugins/sanic-ext/getting-started.html"&gt;Sanic Extensions&lt;/a&gt;. Either way, it's on my to-do list to get this done, but for now I have a pattern to work with Honeycomb in a way that will start to get me the observability metrics I need.&lt;/p&gt;

&lt;h3&gt;
  
  
  Outstanding Issues
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Update Open Telemetry documentation, possibly through PR&lt;/li&gt;
&lt;li&gt;Update GRPC exporter, possibly through PR, to return better errors&lt;/li&gt;
&lt;li&gt;Open issue with Honeycomb for them to update their python documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Takeaways
&lt;/h3&gt;

&lt;p&gt;Like so many efforts, this implementation was born out of necessity. I was somewhat aware of the issues with Open Telemetry and Python because of my long time friend &lt;a href="https://github.com/alertedsnake"&gt;Michael Stella&lt;/a&gt; had already fought some of the same battles.&lt;/p&gt;

&lt;p&gt;I am a bit disappointed with the issues I had to uncover on my own with Honeycomb, but I'm not deterred by them. I'm excited to make it to the next steps for my own efforts, and I hope that this effort will help others, even outside the Sanic community, integrate observability and distributed tracing with their python apps and services.&lt;/p&gt;

&lt;p&gt;With all of that being said - this was a great challenge and I learned lots. I'm very excited about the future of Open Telemetry and establishing a pattern for observability for my own services. I think that if my experience can help anyone shave down time it takes to get up and running with Otel on python, it was totally worth it.&lt;/p&gt;

</description>
      <category>python</category>
      <category>sanic</category>
      <category>opentelemetry</category>
      <category>honeycomb</category>
    </item>
    <item>
      <title>"Speed" in Async Web Frameworks</title>
      <dc:creator>Stephen Sadowski</dc:creator>
      <pubDate>Tue, 16 Jun 2020 13:31:40 +0000</pubDate>
      <link>https://dev.to/sjsadowski/speed-in-async-web-frameworks-196m</link>
      <guid>https://dev.to/sjsadowski/speed-in-async-web-frameworks-196m</guid>
      <description>&lt;p&gt;&lt;em&gt;This was simultaneously published on my &lt;a href="https://sjsadowski.com/speed-in-async-web-frameworks/"&gt;blog&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Over the weekend of 2020-06-06/07, Cal Patterson's post &lt;a href="http://calpaterson.com/async-python-is-not-faster.html"&gt;"Async Python is not faster"&lt;/a&gt; popped on my radar across multiple link aggregators. I agree - async python is not "faster," but I do have some thoughts of my own on the subject.&lt;/p&gt;

&lt;p&gt;To begin I have a few caveats: &lt;br&gt;
a) I am not an expert developer&lt;br&gt;
b) I am not an expert on async&lt;br&gt;
c) I am involved with the Sanic project, a python async web framework&lt;/p&gt;

&lt;p&gt;With those out of the way, I'd like to talk a bit about the value of non-blocking io in a very simple way.&lt;/p&gt;

&lt;p&gt;Consider this (and yes, it too is slightly unrealistic):&lt;br&gt;
You are hungry. You decide you want to eat five hamburgers to satiate your hunger. To get those hamburgers, you go to the nearest quick-serve restaurant (McSynchronous's, or Micky Sync's) and order them.&lt;/p&gt;

&lt;p&gt;This restaurant has one cook and that cook has only the capacity to make one burger at a time. It is not a great set up, but it does the job. It takes some time to cook each burger patty and assemble the burger, but in order to make it easy on you, the restaurant will give you each burger as soon as it is ready. Assuming each burger takes two minutes to make and assemble, after ten minutes you will have all of your burgers.&lt;/p&gt;

&lt;p&gt;This looks like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(1) order
(2) start burger 
(3) prep burger (two minutes)
(4) deliver burger 
(5) repeat steps 2 through 4 four times
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Total time: ten minutes.&lt;/p&gt;

&lt;p&gt;Down the street is another joint (this one is called Async &amp;amp; Await Burger) that also has one cook, but this cook has the capability prep multiple burgers at the same time. For this cook, it also takes two minutes to prep each burger, but because the cook has the advantage of being able to prep multiple burgers together, they can be delivered faster. This is done because the cook does not have to focus on each burger one at a time, but instead can start a burger and then start the next burger, returning to each burger as it is finished.&lt;/p&gt;

&lt;p&gt;This looks like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(1) order
(2) start burgers 1-5
(3) prep burgers 1-5 (two minutes each, simultaneously)
(4) deliver burgers
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Total time: two minutes.&lt;/p&gt;

&lt;p&gt;Finally let us add a restriction to our A&amp;amp;A Burger scenario: there is only capacity to prep one burger at a time. In this scenario, despite the cook being able to handle multiple burgers, it is not possible, and therefore it will take the cook ten minutes, just like at Micky Sync's.&lt;/p&gt;

&lt;p&gt;Imagine, however, that you want just one burger. A&amp;amp;A and Micky Sync's both take the same amount of time to prepare just one burger, so there is no speed advantage at all.&lt;/p&gt;

&lt;p&gt;All of this is the same for synchronous and asynchronous web frameworks and their "speed" in general. The ability for one process to handle multiple simultaneous requests is the advantage of asynchronous operations. If you place a synchronous or blocking request (our capacity to prep only one burger at a time, remember) the advantage is removed.&lt;/p&gt;

&lt;p&gt;I have selected two highly regarded python frameworks to demonstrate this: &lt;a href="https://falconframework.org"&gt;falcon&lt;/a&gt;, served by gunicorn, and &lt;a href="https://www.starlette.io"&gt;starlette&lt;/a&gt;, served by uvicorn. The code is as on parity as I can make them for synchronous and asynchronous as possible:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;falcon:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import falcon
import time

class DefaultResource:
    def on_get(self, req, resp):
        """Handles GET requests"""
        time.sleep(5)
        resp.media = "200 OK"

falconapi = falcon.API()
falconapi.add_route('/', DefaultResource())
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;to serve:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gunicorn app:falconapi&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;starlette:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from starlette.routing import Route
import asyncio

async def default(request):
    await asyncio.sleep(5)
    return PlainTextResponse("200 OK")


starletteapi = Starlette(debug=True, routes=[
    Route('/', default),
])
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;to serve:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;uvicorn app:starletteapi&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;To illustrate this, I used a simple (and old) tool - ab, making 5 requests at the same time as follows:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ab -n 5 -c 5 http://localhost:8000/&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;Results for falcon:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This is ApacheBench, Version 2.3 &amp;lt;$Revision: 1874286 $&amp;gt;
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient).....done


Server Software:        gunicorn/20.0.4
Server Hostname:        localhost
Server Port:            8000

Document Path:          /
Document Length:        8 bytes

Concurrency Level:      5
Time taken for tests:   25.031 seconds
Complete requests:      5
Failed requests:        0
Total transferred:      795 bytes
HTML transferred:       40 bytes
Requests per second:    0.20 [#/sec] (mean)
Time per request:       25031.488 [ms] (mean)
Time per request:       5006.298 [ms] (mean, across all concurrent requests)
Transfer rate:          0.03 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.3      1       1
Processing:  5006 11014 6527.0  12515   20025
Waiting:     5005 11013 6527.2  12515   20024
Total:       5007 11014 6526.9  12516   20025
ERROR: The median and mean for the initial connection time are more than twice the standard
       deviation apart. These results are NOT reliable.

Percentage of the requests served within a certain time (ms)
  50%  10013
  66%  15018
  75%  15018
  80%  20025
  90%  20025
  95%  20025
  98%  20025
  99%  20025
 100%  20025 (longest request)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Results for starlette:&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This is ApacheBench, Version 2.3 &amp;lt;$Revision: 1874286 $&amp;gt;
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient).....done


Server Software:        uvicorn
Server Hostname:        localhost
Server Port:            8000

Document Path:          /
Document Length:        6 bytes

Concurrency Level:      5
Time taken for tests:   10.007 seconds
Complete requests:      5
Failed requests:        0
Total transferred:      695 bytes
HTML transferred:       30 bytes
Requests per second:    0.50 [#/sec] (mean)
Time per request:       10007.417 [ms] (mean)
Time per request:       2001.483 [ms] (mean, across all concurrent requests)
Transfer rate:          0.07 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   0.5      1       1
Processing:  5003 5003   0.2   5003    5004
Waiting:     5002 5003   0.5   5003    5003
Total:       5003 5004   0.6   5004    5005

Percentage of the requests served within a certain time (ms)
  50%   5004
  66%   5005
  75%   5005
  80%   5005
  90%   5005
  95%   5005
  98%   5005
  99%   5005
 100%   5005 (longest request)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally, starlette with a blocking call (time.sleep(5)) instead of await asyncio.sleep(5):&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This is ApacheBench, Version 2.3 &amp;lt;$Revision: 1874286 $&amp;gt;
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient).....done


Server Software:        uvicorn
Server Hostname:        localhost
Server Port:            8000

Document Path:          /
Document Length:        6 bytes

Concurrency Level:      5
Time taken for tests:   25.036 seconds
Complete requests:      5
Failed requests:        0
Total transferred:      695 bytes
HTML transferred:       30 bytes
Requests per second:    0.20 [#/sec] (mean)
Time per request:       25035.827 [ms] (mean)
Time per request:       5007.165 [ms] (mean, across all concurrent requests)
Transfer rate:          0.03 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   0.6      1       2
Processing:  5009 17023 6716.0  20026   20026
Waiting:     5007 17022 6716.5  20026   20026
Total:       5009 17024 6716.6  20028   20028

Percentage of the requests served within a certain time (ms)
  50%  20027
  66%  20028
  75%  20028
  80%  20028
  90%  20028
  95%  20028
  98%  20028
  99%  20028
 100%  20028 (longest request)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As you can see, this is almost exactly the same as falcon.&lt;/p&gt;

&lt;p&gt;With web APIs, things are rarely this simple, and there are other advantages to be found with the level of maturity of the ecosystem (synchronous libraries have had longer time to get things right) and one should never overlook ease of use to make sure things are done correctly, but the general idea is as follows:&lt;/p&gt;

&lt;p&gt;Much of the "speed" of async IO comes from being able to do additional work while the other work is in progress, but does not need attention. In the simple tests above, this is serving a basic GET request, but it applies everywhere. &lt;/p&gt;

</description>
      <category>python</category>
      <category>webdev</category>
      <category>async</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
