<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>ldb</title>
    <link>/</link>
    <description>Recent content on ldb</description>
    <generator>Hugo</generator>
    <language>en</language>
    <lastBuildDate>Sun, 07 Aug 2022 11:30:00 +0000</lastBuildDate>
    <atom:link href="/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Benchmarking Highly Distributed Tracing in OpenTelemetry</title>
      <link>/posts/benchmarking-opentelemetry/</link>
      <pubDate>Sun, 07 Aug 2022 11:30:00 +0000</pubDate>
      <guid>/posts/benchmarking-opentelemetry/</guid>
      <description>&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;&#xA;&lt;p&gt;In recent years, the fields of Internet of Things (IoT) has seen an&#xA;increasing amount of attention. Traditionally the distance between&#xA;internet connected clients and servers has been relatively large, both&#xA;geographically as well as topologically speaking. These clients,&#xA;typically computers, would connect to a set of centralized servers to&#xA;perform some action or retrieve resources. Nowadays, the internet is&#xA;populated much more densely, with the growing number of smart connected&#xA;IoT devices as clients. This scale up pushes the need for service&#xA;providers to move their services closer to these clients in order to&#xA;keep latency low and do avoid single points of failure. Usually this&#xA;comes in the form of Content Delivery Networks (CDNs) or computations&#xA;performed on the edge of the network. Instead of all clients connecting&#xA;to a single centralized set of servers, often deployed on other&#xA;continents, they now connect to regional deployments, sometimes even&#xA;within the same city. This is not only beneficial for the clients, as&#xA;distributing the load across the globe enables more fine grained&#xA;performance and thus cost optimizations. Typically, these edge&#xA;deployments are not very powerful and limited in their ability to scale,&#xA;as their main purpose is not to replace the centralized servers, but&#xA;rather complement them [1]. It is thus important to understand the&#xA;performance characteristics of any software deployed on the edge to&#xA;ensure good reliability and performance for serving potentially tens of&#xA;thousand of the clients.&lt;/p&gt;&#xA;&lt;p&gt;Another field that has been widely explored in the wake of emerging&#xA;cloud systems is that of engineering practices, especially around the&#xA;DevOps methodology and observability of distributed systems. In 2010,&#xA;Sigelman et. al published a technical report about &lt;em&gt;Dapper&lt;/em&gt;, the&#xA;large-scale distributed tracing infrastructure developed at Google at&#xA;that time [2]. Since then a lot of development has been put into cloud&#xA;native systems. Along with it, the tooling for distributed tracing&#xA;evolved with the development of tools like OpenTelemetry&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&#xA;Traditionally, distributed tracing has been applied inside private&#xA;clusters to enable software engineers to accurately pinpoint performance&#xA;bottlenecks and reliability issues in distributed systems by gaining&#xA;insights into processes that span multiple services. In these cloud&#xA;environments, a typical deployment would be used by a couple of hundred&#xA;to a thousand clients simultaneously.&lt;/p&gt;&#xA;&lt;p&gt;However, to our knowledge, not much research has gone into the&#xA;combination of these fields of research, namely highly distributed&#xA;tracing of IoT devices. This combination would allow identifying&#xA;reliability or performance problems in the infrastructure effectively,&#xA;as these devices live the furthest from any centralized server. As they&#xA;are running often in the hands of the customer itself, and thus are the&#xA;most susceptible to different kinds of failures, they offer a unique&#xA;vantage point from which to observe infrastructure. To enable this,&#xA;distributed tracing infrastructure needs to be deployed closer to the&#xA;edge of the network and be made available to potentially tens of&#xA;thousands of clients on the internet, an order of magnitude more than&#xA;what a current typical deployment typically handles.&lt;/p&gt;&#xA;&lt;p&gt;In this paper we want to explore the feasibility of this approach by&#xA;benchmarking the maximum throughput of the OpenTelemetry Collector, the&#xA;reference implementation of the OpenTelemetry standard. We are&#xA;especially interested in the performance characteristics when deployed&#xA;on commodity hardware equipped with limited resources, as is often the&#xA;case in edge computing environments.&lt;/p&gt;&#xA;&lt;p&gt;The rest of the paper is structured as follows. In section&#xA;&lt;a href=&#34;#background&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;background&#34;&gt;2&lt;/a&gt;&#xA;we provide an overview of the specifics of distributed tracing and&#xA;OpenTelemetry in particular. Section&#xA;&lt;a href=&#34;#study-design&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;study-design&#34;&gt;3&lt;/a&gt;&#xA;describes our study design which which we explain in more detail in&#xA;Section&#xA;&lt;a href=&#34;#implementation-details&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;implementation-details&#34;&gt;4&lt;/a&gt;.&#xA;In Section&#xA;&lt;a href=&#34;#results&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;results&#34;&gt;5&lt;/a&gt;&#xA;we present the results of our study which we discuss in Section&#xA;&lt;a href=&#34;#discussion&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;discussion&#34;&gt;6&lt;/a&gt;&#xA;before concluding the paper and giving a brief outlook onto future work&#xA;in Section&#xA;&lt;a href=&#34;#conclusion-and-future-work&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;conclusion-and-future-work&#34;&gt;7&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h1 id=&#34;background&#34;&gt;Background&lt;/h1&gt;&#xA;&lt;p&gt;Whereas traditionally services were created in a monolithic&#xA;architecture, many service providers now opt for a so called micro&#xA;service architecture. In this architecture, a single application is&#xA;split into smaller functional units that can be independently scaled in&#xA;a dynamic fashion. This often leads to a significant reduction in cost,&#xA;but on the other hand is usually considered more complex and thus prone&#xA;to failures. The advancements in development around cloud native micro&#xA;service architectures also increased the need for better tooling to&#xA;collect telemetry from applications deployed in this way. Being able to&#xA;access the logs or metrics of a single service is not enough anymore to&#xA;debug issues, as they are often dependent on other services.&lt;/p&gt;&#xA;&lt;p&gt;Distributed tracing at the core is a method to enable visibility into&#xA;performance and reliability of processes spanning multiple dependent&#xA;services. It’s most basic concept is that of a “trace&amp;quot;. A trace is a&#xA;directed, acyclic graph of spans. Spans, in turn, simply encode the&#xA;duration a certain action takes. The whole system comes together by&#xA;services recording spans for certain actions and sending these spans&#xA;into the centralized tracing infrastructure together with a traceID.&#xA;This traceID can be either generated, or passed between services when a&#xA;communication happens that is related to the same action. Each service&#xA;is responsible for recording its own spans, sending them to a&#xA;centralized tracing service, and finally passing the traceID on to other&#xA;services. Finally, all the spans collected from different services can&#xA;be correlated by their traceID and thus grant visibility into processes&#xA;that span multiple services. These larger collections of spans are&#xA;referred to as traces. The fact that only a traceID needs to be passed&#xA;between services makes distributed tracing a lightweight addition to&#xA;existing communication protocols. On the other hand, tracing, the&#xA;process of recording spans within a service, comes at a cost for the&#xA;service. Existing tooling is often intrusive in that it requires code to&#xA;be added for each span that should be recorded.&lt;/p&gt;&#xA;&lt;p&gt;Figure&#xA;&lt;a href=&#34;#fig:trace_example&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;fig:trace_example&#34;&gt;1&lt;/a&gt;&#xA;depicts an example of a trace as it might have been recorded by two&#xA;services (blue and orange).&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;embed src=&#34;figures/trace.pdf.png&#34; id=&#34;fig:trace_example&#34; /&gt;&lt;figcaption aria-hidden=&#34;true&#34;&gt;Fig. 1: Example of a trace trace containing four spans recorded by two services&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;Open source tools like Jaeger&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;, Zipkin&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt; or OpenTracing&lt;sup id=&#34;fnref:4&#34;&gt;&lt;a href=&#34;#fn:4&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;4&lt;/a&gt;&lt;/sup&gt; emerged&#xA;over the last decade to solve similar problems around distributed&#xA;tracing. Aside from these, a number of vendors sell products using&#xA;proprietary protocols around the collection of traces. Many of them go&#xA;beyond the core of distributed tracing by allowing developers to augment&#xA;spans with additional metadata, such as attaching events or whole log&#xA;lines to spans. However, as these different tools serve very similar use&#xA;cases, the need for a single standard emerged to reduce the cost of&#xA;adoption that is associated with supporting multiple different protocols&#xA;and the risk of vendor lock in.&lt;/p&gt;&#xA;&lt;p&gt;OpenTelemetry&lt;sup id=&#34;fnref1:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt; is an attempt to consolidate these different standards&#xA;for logs, metrics and traces into a single set of APIs, tools and SDKs.&#xA;In this paper we focus only on the OpenTelemetry Collector (OTEL). Its&#xA;purpose is to provide an interfaces to multiple different tracing&#xA;protocols in a single application. Tools like Jaeger then can send all&#xA;their traces to OTEL, which transforms them according to the standard&#xA;and sends them off to another service for processing. While at the&#xA;surface this looks like a simple pipeline for trace data, the collector&#xA;goes beyond that by offering a set of so called “span processors&amp;quot;. These&#xA;are processing stages that can perform additional transformations on the&#xA;incoming data, such as adding or removing metadata from spans, sampling&#xA;spans based on different attributes or derive new telemetry data such as&#xA;metrics or logs from the incoming data.&lt;/p&gt;&#xA;&lt;p&gt;While the OpenTelemetry provides SDKs and implementations for clients,&#xA;as well as the collector, at the point of writing, it does not provide&#xA;any kind of analytics service that allow for storing, querying and&#xA;analyzing any of the collected traces. For this, a third party tools&#xA;needs to be used that accepts telemetry data according to the standard.&lt;/p&gt;&#xA;&lt;h1 id=&#34;study-design&#34;&gt;Study Design&lt;/h1&gt;&#xA;&lt;p&gt;In order to assess the fitness of the OpenTelemetry Collector (OTEL) for&#xA;an edge-deployed scenario we conduct a maximum throughput benchmark. We&#xA;are especially interested in the following three dimensions of the its&#xA;performance quality:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;What is the number of concurrent clients that a single OTEL instance&#xA;can handle?&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;At what sustained rate can this OTEL instance accept traces without&#xA;producing errors?&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Does OTEL introduce any additional latency, especially under&#xA;different configurations?&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Besides the performance under simple forwarding workloads we also take a&#xA;look at two special configurations.&lt;/p&gt;&#xA;&lt;p&gt;The first configuration is one in which OTEL has to mutate the incoming&#xA;data. A sensible use case for this would be clients unintentionally&#xA;sending unwanted metadata, for example sensitive data like customer&#xA;credentials. In such a scenario, this unwanted metadata should be&#xA;stripped from all spans as soon as possible to not violate any&#xA;regulations.&lt;/p&gt;&#xA;&lt;p&gt;Another scenario we look at is sampling. The use case for this would be&#xA;an attempt to limit the amount of incoming data. This can have different&#xA;reasons, for example to reduce the load on systems by dropping data that&#xA;is not interesting.&lt;/p&gt;&#xA;&lt;p&gt;For this, our study is designed as follows. We deploy a single OTEL&#xA;instance on a virtual machine running in Google Cloud. On another&#xA;virtual machine, our benchmarking tool benchd is running. This tool&#xA;creates a number of workers that generate and send a full trace to the&#xA;collector. All workers run concurrently in their own thread and do not&#xA;influence each other in any way. The collector in turn is configured to&#xA;send all traces back to benchd to measure the roundtrip time for each&#xA;trace sent. The number of clients created as well as various parameters&#xA;of the trace created can be configured and randomized.&lt;/p&gt;&#xA;&lt;p&gt;Overall, we are run four different kinds of benchmarks we call “&#xA;benchmark plans&amp;quot;:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;The &lt;em&gt;basic&lt;/em&gt; plan is a raw performance benchmark with minimal&#xA;configuration and randomization. It is used to establish a baseline,&#xA;as well as verify the setup under maximum load.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;The &lt;em&gt;realistic&lt;/em&gt; plan generates a realistic load profile with a&#xA;variable number of spans per trace, as well as extra attributes&#xA;added to each trace.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;The &lt;em&gt;mutate&lt;/em&gt; plan is one in which we introduce a special attribute&#xA;in a fixed percentage of traces by the clients. A matching&#xA;configuration is deployed in OTEL that causes it to drop the special&#xA;attribute.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;The &lt;em&gt;sample&lt;/em&gt; plan is the same as the &lt;em&gt;realistic&lt;/em&gt; one, but now OTEL&#xA;is configured to sample only 25% of all incoming traces.&#xA;Specifically, we perform what is commonly referred to as “head&#xA;sampling&amp;quot; or “probabilistic sampling&amp;quot;, which samples random values&#xA;as they come in. This kind of sampling bases its sampling decision&#xA;soley on the traceID, which makes it very efficient, especially as&#xA;more data with the same traceID arrives. It’s counterpart, “tail&#xA;sampling&amp;quot; works by marking a decision based on a fully received&#xA;trace instead. Depending on the parameters, it can sample based on&#xA;different metadata found throughout the trace. This allows for fine&#xA;grained control over the sampled data. For example the collector can&#xA;be configured to always sample all traces that contain an error.&#xA;However, this approach incurs a severe performance cost, as&#xA;decisions can only be made on a full trace which has to be buffered&#xA;in memory while more data arrives (hence the name “tail sampling&amp;quot;).&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;For each of these workloads, the benchmark consists of two phases. In&#xA;the first phase we create a variable workload to find the supposed&#xA;maximum throughput of the system. In this case, benchd creates new&#xA;clients at a fixed rate, specifically 5 clients every second. The&#xA;benchmark runs until OTEL can not reliably accept traces anymore. The&#xA;indicators for this are that the OTEL process itself crashes (due to&#xA;running out of memory for example), becomes too slow to accept or&#xA;forward traces in a timely manner, or actively rejects new incoming&#xA;traces.&lt;/p&gt;&#xA;&lt;p&gt;In the second phase, we create a static workload with a fixed amount of&#xA;concurrent clients sending traces. The exact number is based on the&#xA;maximum number we saw in phase one, shortly before OTEL stops behaving&#xA;as expected. The workload in this phase then runs for a fixed time of&#xA;thirty minutes in order to assess the sustainability of the peak&#xA;performance from the previous phase.&lt;/p&gt;&#xA;&lt;h1 id=&#34;implementation-details&#34;&gt;Implementation Details&lt;/h1&gt;&#xA;&lt;p&gt;In this section we explain in detail the implementation of our&#xA;benchmark. Specifically, we describe the intrinsics of the benchmarking&#xA;infrastructure as well as the design of benchd, our benchmarking client.&lt;/p&gt;&#xA;&lt;h2 id=&#34;benchmarking-infrastructure&#34;&gt;Benchmarking Infrastructure&lt;/h2&gt;&#xA;&lt;p&gt;As described in Section&#xA;&lt;a href=&#34;#study-design&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;study-design&#34;&gt;3&lt;/a&gt;,&#xA;our benchmark runs on top of virtual machines in Google Cloud. For this&#xA;we use Terraform&lt;sup id=&#34;fnref:5&#34;&gt;&lt;a href=&#34;#fn:5&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;5&lt;/a&gt;&lt;/sup&gt; to create the machines and all supporting&#xA;infrastructure such as firewall rules. It is also used to generate and&#xA;deploy all necessary configurations. Unlike common in many cloud&#xA;environments, we opted to not deploy OTEL in a container or use any&#xA;orchestration tool such as Kubernetes as to not impair the performance&#xA;of the service which could skew our benchmarking results[3].&lt;/p&gt;&#xA;&lt;p&gt;In total, we deploy three virtual machines inside a single region and&#xA;availability zone. We use version 0.43 of the OpenTelemetry Collector in&#xA;the &amp;ldquo;contrib&amp;rdquo; distribution&lt;sup id=&#34;fnref:6&#34;&gt;&lt;a href=&#34;#fn:6&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;6&lt;/a&gt;&lt;/sup&gt;. This distribution is special in that it&#xA;contains the set of processors needed to run the collector in the&#xA;sampling and mutating configuration. OTEL is running on commodity&#xA;hardware, an instance of type &lt;em&gt;e2-standard-2&lt;/em&gt; with 2 vCPU and 8 GiB of&#xA;memory, running Debian 11. The only performance optimization we apply is&#xA;to raise the file descriptor limits for the OTEL process to allow it to&#xA;make the best use of the hardware.&lt;/p&gt;&#xA;&lt;p&gt;The instance running our benchmarking client benchd uses an&#xA;&lt;em&gt;e2-standard-4&lt;/em&gt; with 4 vCPU and 16 GiB of memory. This is to ensure that&#xA;benchd does not become a bottleneck in our measurements. Similiarly, we&#xA;raise the file descriptor limits here.&lt;/p&gt;&#xA;&lt;p&gt;Additionally, we deploy a third instance running monitoring software&#xA;that collects various metrics from both instances running OTEL and&#xA;benchd every second. These metrics include system metrics like CPU time,&#xA;memory usage, disk utilization and network utilization, as well as&#xA;process metrics like current number of active clients, the current send&#xA;rate and latency in benchd or accepted and rejected spans in OTEL. This&#xA;helps us ensure that we can identify failures or bottlenecks that can&#xA;invalidate our benchmark early. It also enables us to run quick&#xA;exploratory benchmarks to test assumptions without having to analyze the&#xA;results file created by benchd. Lastly, we can correlate the system&#xA;metrics to certain behaviour in OTEL.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-benchmarking-client&#34;&gt;The Benchmarking Client&lt;/h2&gt;&#xA;&lt;p&gt;As mentioned in Section&#xA;&lt;a href=&#34;#study-design&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;study-design&#34;&gt;3&lt;/a&gt;,&#xA;our benchmarking client benchd runs a number of concurrent clients we&#xA;call workers. Each worker is essentially a loop that performs the&#xA;following four operations: 1) generate a trace, 2) flush the trace to&#xA;the collector, 3) wait for the trace to return back to benchd, and 4)&#xA;log the results. Traces are generated recursively calling a function&#xA;that generates spans. This causes all spans to be nested, creating a&#xA;“parent-child&amp;quot; relationship. While this is a realistic model for single&#xA;threaded applications, it eliminates the possibility of creating&#xA;“sibling-spans&amp;quot;, which are being created in parallel of one another.&#xA;Each trace sent produces exactly one log line containing the following&#xA;information: A timestamp for the log line itself, the ID of the worker,&#xA;the final state of the request, the parameters of the trace that was&#xA;generated, the time the current loop iteration started, and finally the&#xA;start and end times of both the sending and receiving operations. All&#xA;timestamps are collected with millisecond precision. Aside from that,&#xA;each request can finish in one of several states: success, send timeout,&#xA;send error, received timeout, worker shutdown.&lt;/p&gt;&#xA;&lt;p&gt;This fine grained logging not only allows us to perform an analysis on&#xA;multiple dimensions, but also can be used to recreate the results easily&#xA;by feeding the result log back into benchd. In order to get comparable&#xA;and meaningful results, we resample all log lines to a granularity of 1&#xA;second.&lt;/p&gt;&#xA;&lt;h1 id=&#34;results&#34;&gt;Results&lt;/h1&gt;&#xA;&lt;p&gt;In this section we present the results for each of the four benchmark&#xA;plans as described in Section&#xA;&lt;a href=&#34;#study-design&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;study-design&#34;&gt;3&lt;/a&gt;.&#xA;Table&#xA;&lt;a href=&#34;#table:plan_parameters&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;table:plan_parameters&#34;&gt;1&lt;/a&gt;&#xA;shows the parameters which were used to generate the workload for each&#xA;plan. All of these values are an upper bound for the respective&#xA;parameter. At runtime, each worker randomly selects a number for each&#xA;parameter between 0 and the configured number. These numbers are then&#xA;logged by the worker.&lt;/p&gt;&#xA;&lt;div class=&#34;center&#34;&gt;&#xA;&lt;div id=&#34;table:plan_parameters&#34;&gt;&#xA;&lt;table&gt;&#xA;&lt;caption&gt;Table 1: Benchmark Plan Parameters&lt;/caption&gt;&#xA;&lt;thead&gt;&#xA;&lt;tr class=&#34;header&#34;&gt;&#xA;&lt;th style=&#34;text-align: center;&#34;&gt;Plan&lt;/th&gt;&#xA;&lt;th style=&#34;text-align: center;&#34;&gt;Spans&lt;/th&gt;&#xA;&lt;th style=&#34;text-align: center;&#34;&gt;Span Duration&lt;/th&gt;&#xA;&lt;th style=&#34;text-align: center;&#34;&gt;Extra Attributes&lt;/th&gt;&#xA;&lt;th style=&#34;text-align: center;&#34;&gt;Risk&lt;/th&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/thead&gt;&#xA;&lt;tbody&gt;&#xA;&lt;tr class=&#34;odd&#34;&gt;&#xA;&lt;td style=&#34;text-align: center;&#34;&gt;basic&lt;/td&gt;&#xA;&lt;td style=&#34;text-align: center;&#34;&gt;10&lt;/td&gt;&#xA;&lt;td style=&#34;text-align: center;&#34;&gt;100ms&lt;/td&gt;&#xA;&lt;td style=&#34;text-align: center;&#34;&gt;0&lt;/td&gt;&#xA;&lt;td style=&#34;text-align: center;&#34;&gt;0%&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr class=&#34;even&#34;&gt;&#xA;&lt;td style=&#34;text-align: center;&#34;&gt;realistic&lt;/td&gt;&#xA;&lt;td style=&#34;text-align: center;&#34;&gt;20&lt;/td&gt;&#xA;&lt;td style=&#34;text-align: center;&#34;&gt;250ms&lt;/td&gt;&#xA;&lt;td style=&#34;text-align: center;&#34;&gt;10&lt;/td&gt;&#xA;&lt;td style=&#34;text-align: center;&#34;&gt;0%&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr class=&#34;odd&#34;&gt;&#xA;&lt;td style=&#34;text-align: center;&#34;&gt;mutate&lt;/td&gt;&#xA;&lt;td style=&#34;text-align: center;&#34;&gt;20&lt;/td&gt;&#xA;&lt;td style=&#34;text-align: center;&#34;&gt;250ms&lt;/td&gt;&#xA;&lt;td style=&#34;text-align: center;&#34;&gt;10&lt;/td&gt;&#xA;&lt;td style=&#34;text-align: center;&#34;&gt;50%&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;tr class=&#34;even&#34;&gt;&#xA;&lt;td style=&#34;text-align: center;&#34;&gt;sample&lt;/td&gt;&#xA;&lt;td style=&#34;text-align: center;&#34;&gt;20&lt;/td&gt;&#xA;&lt;td style=&#34;text-align: center;&#34;&gt;250ms&lt;/td&gt;&#xA;&lt;td style=&#34;text-align: center;&#34;&gt;10&lt;/td&gt;&#xA;&lt;td style=&#34;text-align: center;&#34;&gt;0%&lt;/td&gt;&#xA;&lt;/tr&gt;&#xA;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;For these parameters, &lt;em&gt;Spans&lt;/em&gt; refers to the maximum number of spans&#xA;contained within a single trace. &lt;em&gt;Span Length&lt;/em&gt; is the maximum duration&#xA;for a single span within the trace. &lt;em&gt;Extra Attributes&lt;/em&gt; refers to the&#xA;maximum number of extra attributes added to each span in the trace.&#xA;Finally, &lt;em&gt;Risk&lt;/em&gt; describes the percentage with which a &amp;ldquo;risky attribute&amp;rdquo;&#xA;is added to a random span within the trace. For example, in the &lt;em&gt;mutate&lt;/em&gt;&#xA;plan, each trace has a 50% chance of containing the risky attribute and&#xA;thus being mutated by the collector.&lt;/p&gt;&#xA;&lt;h2 id=&#34;basic-plan&#34;&gt;Basic Plan&lt;/h2&gt;&#xA;&lt;p&gt;The workload generated in the basic plan is very synthetic and small.&#xA;Fig.&#xA;&lt;a href=&#34;#fig:basic_number_clients_send_rate&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;fig:basic_number_clients_send_rate&#34;&gt;2&lt;/a&gt;&#xA;shows the number of active workers in blue and the traces sent by these&#xA;workers over time. The red dashed line is the first occurrence of an&#xA;error during the benchmark. We can see this happening shortly before the&#xA;20 minute runtime mark. At this point, about 5800 workers are sending at&#xA;an accumulative rate of about 2500 traces per second.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;embed src=&#34;figures/benchd_basic-50_number_clients_send_rate.pdf.png&#34; id=&#34;fig:basic_number_clients_send_rate&#34; /&gt;&lt;figcaption aria-hidden=&#34;true&#34;&gt;Fig. 2: Number of active workers and traces sent per second in the basic plan&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;We can also see that for the first 4 minutes the sending rate steadily&#xA;increases. That’s when it reaches a tipping point at just over 3000&#xA;workers and a sending rate of 1500 traces per second. After that, the&#xA;rate becomes increasingly unstable, fluctuating at values between 2000&#xA;and 2500 traces per second.&lt;/p&gt;&#xA;&lt;p&gt;Fig.&#xA;&lt;a href=&#34;#fig:basic_rate_send_latency&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;fig:basic_rate_send_latency&#34;&gt;3&lt;/a&gt;&#xA;and Fig.&#xA;&lt;a href=&#34;#fig:basic_rate_receive_latency&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;fig:basic_rate_receive_latency&#34;&gt;4&lt;/a&gt;&#xA;depict the trace rate and send and received latencies, respectively. We&#xA;can see that while the median send latency is relatively stable at under&#xA;20ms, the 90th and 95th percentile values are significantly higher, at&#xA;about 60ms and 80ms. In comparison, the receive latency does steadily&#xA;increase, even when the sending rate itself begins to stagnate. This&#xA;indicates that the collector is now fully saturated, and that new traces&#xA;are not passed in at the same rate they arrive at.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;embed src=&#34;figures/benchd_basic-50_send_rate_send_latency.pdf.png&#34; id=&#34;fig:basic_rate_send_latency&#34; /&gt;&lt;figcaption aria-hidden=&#34;true&#34;&gt;Fig. 3: Traces sent per second and send latency as 50th, 90th, and 95th percentile in the basic plan&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;figure&gt;&#xA;&lt;embed src=&#34;figures/benchd_basic-50_send_rate_receive_latency.pdf.png&#34; id=&#34;fig:basic_rate_receive_latency&#34; /&gt;&lt;figcaption aria-hidden=&#34;true&#34;&gt;Fig. 4: Traces sent per second and receive latency as 50th, 90th, and 95th percentile in the basic plan&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;h2 id=&#34;realistic-plan&#34;&gt;Realistic Plan&lt;/h2&gt;&#xA;&lt;p&gt;The realistic plan creates a workload that is less synthetic than the&#xA;basic one with twice as many spans per trace, higher span duration and&#xA;additional attributes added to the traces. Fig&#xA;&lt;a href=&#34;#fig:realistic_number_clients_send_rate&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;fig:realistic_number_clients_send_rate&#34;&gt;5&lt;/a&gt;&#xA;shows the number of active workers and trace rate over time. We can see&#xA;that the first error again appears shortly before the 20 minute runtime&#xA;mark. It is also visible, that compared to the basic plan in Fig.&#xA;&lt;a href=&#34;#fig:basic_number_clients_send_rate&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;fig:basic_number_clients_send_rate&#34;&gt;2&lt;/a&gt;,&#xA;the trace rate increases at a slower rate. While the basic plan achieves&#xA;a throughput of about 2000 traces per second after about 4 minutes, the&#xA;realistic plan needs about 12 minutes. At the same time, the trace rate&#xA;in Fig.&#xA;&lt;a href=&#34;#fig:realistic_number_clients_send_rate&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;fig:realistic_number_clients_send_rate&#34;&gt;5&lt;/a&gt;&#xA;is stable for a much longer time, too. As the number of workers&#xA;increases at the same rate as in the basic plan, we can conclude that&#xA;the instability in trace rate we see later in the benchmarks does not&#xA;stem from the number of workers sending traces, but is purely dependant&#xA;on the trace rate itself.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;embed src=&#34;figures/benchd_realistic-50_number_clients_send_rate.pdf.png&#34; id=&#34;fig:realistic_number_clients_send_rate&#34; /&gt;&lt;figcaption aria-hidden=&#34;true&#34;&gt;Fig. 5: Number of active workers and traces sent per second in the realistic plan&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;Fig.&#xA;&lt;a href=&#34;#fig:realistic_rate_send_latency&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;fig:realistic_rate_send_latency&#34;&gt;6&lt;/a&gt;&#xA;shows the trace rate and send latency in different percentiles for the&#xA;realistic plan. We can see that compared to the basic plan, the send&#xA;latency at the end of the run is about twice as high for the 90th and&#xA;95th percentile, while the median latency is about the same at around&#xA;20ms to 25ms.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;embed src=&#34;figures/benchd_realistic-50_send_rate_send_latency.pdf.png&#34; id=&#34;fig:realistic_rate_send_latency&#34; /&gt;&lt;figcaption aria-hidden=&#34;true&#34;&gt;Fig. 6: Traces sent per second and send latency as 50th, 90th, and 95th percentile in the realistic plan&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;The trace rate compared with the receive latency for the realistic plan&#xA;is depicted in Fig.&#xA;&lt;a href=&#34;#fig:realistic_rate_receive_latency&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;fig:realistic_rate_receive_latency&#34;&gt;7&lt;/a&gt;.&#xA;Compared to the basic plan, this one is significantly lower with about&#xA;1000ms to 1200ms in the 95th percentile at a rate of 2000 traces per&#xA;second on the realistic plan compared to the 1500ms to 2000ms.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;embed src=&#34;figures/benchd_realistic-50_send_rate_receive_latency.pdf.png&#34; id=&#34;fig:realistic_rate_receive_latency&#34; /&gt;&lt;figcaption aria-hidden=&#34;true&#34;&gt;Fig. 7: Traces sent per second and receive latency as 50th, 90th, and 95th percentile in the realistic plan&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;h2 id=&#34;mutate-plan&#34;&gt;Mutate Plan&lt;/h2&gt;&#xA;&lt;p&gt;As shown in Table&#xA;&lt;a href=&#34;#table:plan_parameters&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;table:plan_parameters&#34;&gt;1&lt;/a&gt;,&#xA;the mutate plan is identical to the realistic plan, with only the&#xA;difference that 50% of traces contain a special attribute that is being&#xA;filtered out by the collector. Fig.&#xA;&lt;a href=&#34;#fig:mutate_number_clients_send_rate&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;fig:mutate_number_clients_send_rate&#34;&gt;8&lt;/a&gt;,&#xA;&lt;a href=&#34;#fig:mutate_rate_send_latency&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;fig:mutate_rate_send_latency&#34;&gt;9&lt;/a&gt;,&#xA;and&#xA;&lt;a href=&#34;#fig:mutate_rate_receive_latency&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;fig:mutate_rate_receive_latency&#34;&gt;10&lt;/a&gt;&#xA;again show the number of active workers, the trace rate, the send&#xA;latency, and the receive latency. We can see that the numbers for all of&#xA;these metrics are nearly identical with those of the realistic plan.&#xA;This makes sense, because the workload is identical, too. On the other&#xA;hand, one might expect that mutating the traces before sending them back&#xA;to the benchmarking client would incur some kind of processing overhead,&#xA;that should be visible in a higher receive latency. Fig.&#xA;&lt;a href=&#34;#fig:mutate_rate_receive_latency&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;fig:mutate_rate_receive_latency&#34;&gt;10&lt;/a&gt;&#xA;however shows, that the 95th percentile of the read latency barely&#xA;exceeds 1000ms, while its counterpart in the realistic plan in Fig.&#xA;&lt;a href=&#34;#fig:realistic_rate_receive_latency&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;fig:realistic_rate_receive_latency&#34;&gt;7&lt;/a&gt;&#xA;clearly shows peaks at 20% higher than that.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;embed src=&#34;figures/benchd_mutate-50_number_clients_send_rate.pdf.png&#34; id=&#34;fig:mutate_number_clients_send_rate&#34; /&gt;&lt;figcaption aria-hidden=&#34;true&#34;&gt;Fig. 8: Number of active workers and traces sent per second in the mutate plan&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;figure&gt;&#xA;&lt;embed src=&#34;figures/benchd_mutate-50_send_rate_send_latency.pdf.png&#34; id=&#34;fig:mutate_rate_send_latency&#34; /&gt;&lt;figcaption aria-hidden=&#34;true&#34;&gt;Fig. 9: Traces sent per second and send latency as 50th, 90th, and 95th percentile in the mutate plan&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;figure&gt;&#xA;&lt;embed src=&#34;figures/benchd_mutate-50_send_rate_receive_latency.pdf.png&#34; id=&#34;fig:mutate_rate_receive_latency&#34; /&gt;&lt;figcaption aria-hidden=&#34;true&#34;&gt;Fig. 10: Traces sent per second and receive latency as 50th, 90th, and 95th percentile in the mutate plan&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;h2 id=&#34;sustained-load-comparison&#34;&gt;Sustained Load Comparison&lt;/h2&gt;&#xA;&lt;p&gt;As described in Section&#xA;&lt;a href=&#34;#study-design&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;study-design&#34;&gt;3&lt;/a&gt;,&#xA;for each of the plans, we execute a second, 30 minute long run to verify&#xA;the sustainability of the detected peak performance. In Fig.&#xA;&lt;a href=&#34;#fig:basic_number_clients_send_rate&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;fig:basic_number_clients_send_rate&#34;&gt;2&lt;/a&gt;,&#xA;&lt;a href=&#34;#fig:realistic_number_clients_send_rate&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;fig:realistic_number_clients_send_rate&#34;&gt;5&lt;/a&gt;&#xA;and&#xA;&lt;a href=&#34;#fig:mutate_number_clients_send_rate&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;fig:mutate_number_clients_send_rate&#34;&gt;8&lt;/a&gt;&#xA;we can see that the peak load for all three plans is at about 5000&#xA;workers.&lt;/p&gt;&#xA;&lt;p&gt;Fig.&#xA;&lt;a href=&#34;#fig:benchd_60_sustained_throughput&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;fig:benchd_60_sustained_throughput&#34;&gt;11&lt;/a&gt;&#xA;shows the trace rates of each plan when run for 30 minutes using 5000&#xA;workers. In order to denoise the output, we apply a 60 second moving&#xA;average. We can clearly see, that the maximum sustained throughput of&#xA;the basic plan is significantly higher, at a rate of 2200 to 2600 traces&#xA;per second. As already seen before, the realistic and mutate plan are&#xA;nearly identical. However, we can also see, that the mutate plan appears&#xA;to achieve a slightly higher trace rate on average.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;embed src=&#34;figures/benchd_60_sustained_throughput.pdf.png&#34; id=&#34;fig:benchd_60_sustained_throughput&#34; /&gt;&lt;figcaption aria-hidden=&#34;true&#34;&gt;Fig. 11: Sustained trace rate of 5000 workers under the basic, realistic and mutate plan with a 60 second moving average window&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;Fig.&#xA;&lt;a href=&#34;#fig:benchd_30_sustained_throughput&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;fig:benchd_30_sustained_throughput&#34;&gt;12&lt;/a&gt;&#xA;shows the same underlying data as Fig.&#xA;&lt;a href=&#34;#fig:benchd_60_sustained_throughput&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;fig:benchd_60_sustained_throughput&#34;&gt;11&lt;/a&gt;.&#xA;This time however, we apply a 30 seconds moving average window. We can&#xA;now see a periodic pattern with a cycle period of about 1 minute.&#xA;Interestingly, this pattern is perfectly synchronized across all runs.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;embed src=&#34;figures/benchd_30_sustained_throughput.pdf.png&#34; id=&#34;fig:benchd_30_sustained_throughput&#34; /&gt;&lt;figcaption aria-hidden=&#34;true&#34;&gt;Fig. 12: Sustained trace rate of 5000 workers under the basic, realistic and mutate plan with a 30 second moving average window&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;h2 id=&#34;sample-plan&#34;&gt;Sample Plan&lt;/h2&gt;&#xA;&lt;p&gt;Due to the architecture of our benchmarking client, we consider the&#xA;sample plan separately from the others. In this configuration, the&#xA;collector drops 75% of all incoming traces. Only the 25% that were&#xA;sampled actually return to benchd. Since it has no way to identify which&#xA;traces get dropped by the collector, the only way to detect this is for&#xA;it to receive timeout while waiting for the trace to return. Because of&#xA;this, metrics like trace throughput and receive latency can not be&#xA;compared to other configurations without caveats.&lt;/p&gt;&#xA;&lt;p&gt;Instead, we first look at the rate of successfully received traces&#xA;compared to the number of receive timeouts. Fig.&#xA;&lt;a href=&#34;#fig:benchd_sample-100-sustain_send_rate_timeout_rate&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;fig:benchd_sample-100-sustain_send_rate_timeout_rate&#34;&gt;13&lt;/a&gt;&#xA;shows these rates in the 30 minute sustained workload with 5000 active&#xA;workers. We can see, that the number of received timeouts is three times&#xA;as high as the number of successfully received traces at 1800 traces per&#xA;second. This matches exactly the expectation, that 75% of traces sent&#xA;result in a receive timeout by not being sampled. We can also calculate&#xA;that the resulting overall send throughput is about 2400 traces per&#xA;second. In this, the sample plan performs better than the realistic and&#xA;mutate plan, but slightly worse than the basic plan.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;embed src=&#34;figures/benchd_sample-100-sustain_send_rate_timeout_rate.pdf.png&#34; id=&#34;fig:benchd_sample-100-sustain_send_rate_timeout_rate&#34; /&gt;&lt;figcaption aria-hidden=&#34;true&#34;&gt;Fig. 13: Sustained rate of successfully received traces and receive timeouts of 5000 workers in the sample plan&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;Fig.&#xA;&lt;a href=&#34;#fig:benchd_sample-100-sustain_send_rate_receive_latency&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;fig:benchd_sample-100-sustain_send_rate_receive_latency&#34;&gt;14&lt;/a&gt;&#xA;shows the rate of successfully received traces and their latency in the&#xA;same sustained workload scenario. Comparing the receive latency of this&#xA;run at a receive rate of 500 to 600 traces per second with all other&#xA;plans, we can see that the sample plan performs significantly better,&#xA;with the 99th percentile being at least ten times lower than in all&#xA;other plans.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;embed src=&#34;figures/benchd_sample-100-sustain_send_rate_receive_latency.pdf.png&#34; id=&#34;fig:benchd_sample-100-sustain_send_rate_receive_latency&#34; /&gt;&lt;figcaption aria-hidden=&#34;true&#34;&gt;Fig. 14: ustained rate of successfully received traces and their latency of 5000 workers in the sample plan&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;h1 id=&#34;discussion&#34;&gt;Discussion&lt;/h1&gt;&#xA;&lt;p&gt;From the results presented in Section&#xA;&lt;a href=&#34;#results&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;results&#34;&gt;5&lt;/a&gt;&#xA;we can learn several things about the OpenTelemetry Collector.&lt;/p&gt;&#xA;&lt;p&gt;Firstly, comparing the basic and realistic plan shows, that the maximum&#xA;sustainable throughput of our system under test primarily depends on the&#xA;size of the payload. Contrary to our assumption, performing additional&#xA;transformations on the traces does not incur a measurable performance&#xA;cost. In fact, the opposite appears to be true, as the configuration&#xA;that mutates the input trace produced a slightly higher throughput.&#xA;However, the results in this regard are far from exhaustive. In our&#xA;benchmark, we perform only one mutation on the input data, by removing a&#xA;single attribute. We deem this a realistic scenario, but performing more&#xA;extensive processing might still yield a very different result that is&#xA;in line with the assumption that additional processing comes at a cost.&lt;/p&gt;&#xA;&lt;p&gt;Secondly, our results show, that under high load the collector can&#xA;introduce up to 2 seconds of latency when forwarding traces. This should&#xA;be considered in architectures where traces need to be made available in&#xA;real time. Especially in architectures where a hierarchy of collectors&#xA;is used this is important to consider, as latency can quickly accumulate&#xA;at each step.&lt;/p&gt;&#xA;&lt;p&gt;On the other hand, our results clearly show, that sampling is an&#xA;effective way to reduce the latency of the provider to forward any&#xA;received traces. This should not be surprising, as parts of the data are&#xA;simply discarded here. In many situations, this can be a viable&#xA;trade-off though, as often only a small fraction of traces are&#xA;interesting. As already briefly mentioned in Section&#xA;&lt;a href=&#34;#study-design&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;study-design&#34;&gt;3&lt;/a&gt;,&#xA;tail sampling could be a solution to this problem by making sure to&#xA;sample based on attributes deemed interesting. However, this decision&#xA;should be heavily informed by the overall tracing architecture. It&#xA;usually is not advisable to perform tail sampling as the first step in a&#xA;distributed tracing pipeline, as traces are still not complete at this&#xA;stage and the sampling decision can be influenced by related spans from&#xA;other applications.&lt;/p&gt;&#xA;&lt;p&gt;A surprising result is the cyclic pattern we identified when measuring&#xA;sustained throughput. Neither the documentation of the collector nor its&#xA;codebase indicate any kind of buffering of batching of results taking&#xA;place [4]. The shape of the measurement supports this finding.&#xA;Batching or buffering usually works by filling up a fixed sized buffer&#xA;and clearing it all at once. This would produce a shape similar to a&#xA;sawtooth with a fixed amplitude. Our measurements on the other hand show&#xA;a pattern more akin to a sine wave with varying amplitude.&lt;/p&gt;&#xA;&lt;p&gt;Our results also reveal shortcomings in the study design, specifically&#xA;when using a sampling configuration. Because the way &lt;em&gt;benchd&lt;/em&gt; is&#xA;designed, it cannot detect that a trace was discarded other than by&#xA;timing out while waiting for a response. This influences directly each&#xA;worker’s output speed, as new traces are only sent if previous ones are&#xA;successfully received. An improved architecture could decouple the load&#xA;generation and receiving components to work independently. Each trace&#xA;sent would be logged, along with its traceID. Independently, all&#xA;incoming traces are also logged with their traceID. This would shift the&#xA;work of correlating sent and received traces to a later stage in the&#xA;process, but would presumably yield a more accurate workload generation.&lt;/p&gt;&#xA;&lt;h1 id=&#34;conclusion-and-future-work&#34;&gt;Conclusion and Future Work&lt;/h1&gt;&#xA;&lt;p&gt;As the internet continues to grow in new ways with an ever increasing&#xA;number of smart devices being used every day, so does the need for&#xA;tooling that can help developers measure the performance and reliability&#xA;of these devices. Distributed tracing, with it’s focus on giving a&#xA;holistic view on the dynamics of distributed systems lends itself&#xA;especially well for this. By performing a maximum throughput benchmark&#xA;under multiple configurations that represent real-world use cases, we&#xA;were able to uncover several performance characteristics of the&#xA;OpenTelemetry Collector, the reference implementation for the&#xA;OpenTelemetry standard. First, we show that the amount of input data per&#xA;trace has the biggest influence on the maximum sustainable throughput.&#xA;Secondly, we show that mutations, at least on a small scale do not&#xA;impact the performance of the collector at all. Contrary to our&#xA;assumptions, the collector performed slightly better when removing a&#xA;certain attribute from incoming traces. Furthermore, we show that&#xA;sampling can be a viable trade-off when compute resources for the&#xA;collector are scarce. Not only is the collector able to accept traces at&#xA;a higher rate when sampling, we also see a significant speedup in the&#xA;time it takes for the collector to send sampled traces on. Finally we&#xA;show that under all configurations the collector can sustain the peak&#xA;load of 5000 clients sending between 2000 and 3000 traces with up to 20&#xA;spans per second for at least 30 minutes while running on commodity&#xA;hardware with 2 vCPU and 8 GiB of memory.&lt;/p&gt;&#xA;&lt;p&gt;All these results leave us to conclude that the OpenTelemetry Collector&#xA;is a good choice for being deployed on the edge of the network, an&#xA;environment that is often resource constrained and requires high&#xA;performance to cope with the load created by thousands of clients.&lt;/p&gt;&#xA;&lt;p&gt;To paint a clearer picture, future research can be conducted by&#xA;benchmarking a wider range of different configurations. Especially&#xA;different sampling strategies and data mutations should be further&#xA;explored. Besides that, entirely new processing methods, such as&#xA;deriving metrics from incoming data, filtering data based on regular&#xA;expression matches against attributes, grouping, batching, and routing&#xA;are worth looking at. All these can have valid use cases in an edge&#xA;deployment and significantly impact the performance of the collector.&#xA;Another opportunity for future research is to eliminate shortcomings of&#xA;the current design of our benchmarking client. Namely, this could&#xA;include splitting the client into two functional pieces that are soley&#xA;responsible for sending and receiveing telemetry data, respectively, as&#xA;described in Section&#xA;&lt;a href=&#34;#discussion&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;discussion&#34;&gt;6&lt;/a&gt;.&#xA;Two other possible improvements are the ability to send “sibling-spans&amp;quot;&#xA;as described in Section&#xA;&lt;a href=&#34;#implementation-details&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;implementation-details&#34;&gt;4&lt;/a&gt;&#xA;as well as sending incomplete traces or related spans from multiple&#xA;clients. Lastly, we deem it worthwhile to also benchmark different tools&#xA;such as Zipkin or Jaeger. OpenTelemetry has emerged as a consolidating&#xA;standard, however this should not be the only deciding factor when&#xA;designing a system that enables developers to perform highly distributed&#xA;tracing on smart devices.&lt;/p&gt;&#xA;&lt;h1 id=&#34;references&#34;&gt;References&lt;/h1&gt;&#xA;&lt;div id=&#34;refs&#34; class=&#34;references csl-bib-body&#34;&gt;&#xA;&lt;div id=&#34;ref-yu_survey_2018&#34; class=&#34;csl-entry&#34;&gt;&#xA;&lt;p&gt;&lt;span class=&#34;csl-left-margin&#34;&gt;[1] &lt;/span&gt;&lt;span&#xA;class=&#34;csl-right-inline&#34;&gt;W. Yu &lt;em&gt;et al.&lt;/em&gt;, “A Survey on the Edge Computing&#xA;for the Internet of Things,” &lt;em&gt;IEEE Access&lt;/em&gt;, vol. 6, pp. 6900–6919, 2018,&#xA;doi:&#xA;&lt;a href=&#34;https://doi.org/10.1109/ACCESS.2017.2778504&#34;&gt;10.1109/ACCESS.2017.2778504&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;&#xA;&lt;/div&gt;&#xA;&lt;div id=&#34;ref-sigelman_dapper_nodate&#34; class=&#34;csl-entry&#34;&gt;&#xA;&lt;p&gt;&lt;span class=&#34;csl-left-margin&#34;&gt;[2] &lt;/span&gt;&lt;span&#xA;class=&#34;csl-right-inline&#34;&gt;B. H. Sigelman &lt;em&gt;et al.&lt;/em&gt;, “Dapper, a Large-Scale&#xA;Distributed Systems Tracing Infrastructure,” p. 14.&lt;/span&gt;&lt;/p&gt;&#xA;&lt;/div&gt;&#xA;&lt;div id=&#34;ref-wang_performance_2016&#34; class=&#34;csl-entry&#34;&gt;&#xA;&lt;p&gt;&lt;span class=&#34;csl-left-margin&#34;&gt;[3] &lt;/span&gt;&lt;span&#xA;class=&#34;csl-right-inline&#34;&gt;B. Ruan, H. Huang, S. Wu, and H. Jin, “A&#xA;Performance Study of Containers in Cloud Environment,” in &lt;em&gt;Advances in&#xA;Services Computing&lt;/em&gt;, vol. 10065, G. Wang, Y. Han, and G. Martínez Pérez,&#xA;Eds. Cham: Springer International Publishing, 2016, pp. 343–356. doi:&#xA;&lt;a href=&#34;https://doi.org/10.1007/978-3-319-49178-3_27&#34;&gt;10.1007/978-3-319-49178-3_27&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;&#xA;&lt;/div&gt;&#xA;&lt;div id=&#34;ref-the_opentelemetry_authors_opentelemetry_2022&#34;&#xA;class=&#34;csl-entry&#34;&gt;&#xA;&lt;p&gt;&lt;span class=&#34;csl-left-margin&#34;&gt;[4] &lt;/span&gt;&lt;span&#xA;class=&#34;csl-right-inline&#34;&gt;T. O. Authors, “OpenTelemetry Documentation,”&#xA;&lt;em&gt;OpenTelemetry Documentation&lt;/em&gt;. Feb. 2022. Available:&#xA;&lt;a href=&#34;https://opentelemetry.io/docs/&#34;&gt;https://opentelemetry.io/docs/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://opentelemetry.io&#34;&gt;https://opentelemetry.io&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref1:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://www.jaegertracing.io&#34;&gt;https://www.jaegertracing.io&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:3&#34;&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://zipkin.io&#34;&gt;https://zipkin.io&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:3&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:4&#34;&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://opentracing.io&#34;&gt;https://opentracing.io&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:4&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:5&#34;&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://www.terraform.io/&#34;&gt;https://www.terraform.io/&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:5&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:6&#34;&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/open-telemetry/opentelemetry-collector-contrib&#34;&gt;https://github.com/open-telemetry/opentelemetry-collector-contrib&lt;/a&gt;&amp;#160;&lt;a href=&#34;#fnref:6&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
    </item>
    <item>
      <title>Large Scale Outage Visibility on the Control Plane</title>
      <link>/publications/outage-visibility-control-plane/</link>
      <pubDate>Wed, 01 Dec 2021 00:00:00 +0000</pubDate>
      <guid>/publications/outage-visibility-control-plane/</guid>
      <description>&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;&#xA;&lt;p&gt;The Internet was built as a robust &lt;em&gt;infrastructure&lt;/em&gt; that can tolerate&#xA;infrastructure failures. Yet, the concentration of Internet &lt;em&gt;services&lt;/em&gt;&#xA;to few but large clusters (e.g., data centers) can challenge its&#xA;resilience to failures. In this regard, attacks and infrastructure&#xA;failures of such clusters that concentrate many services can lead to&#xA;noticeable outages. While outages of various forms have been studied&#xA;before [1]–[3], studying the &lt;em&gt;visibility&lt;/em&gt; of outages in service&#xA;provider networks itself has received only little attention [4]; an&#xA;aspect we address.&lt;/p&gt;&#xA;&lt;p&gt;In this paper, we analyze the visibility of such network-specific&#xA;large-scale outages on the Internet’s control plane. That is, we analyze&#xA;an outage in the Cloudflare network on July 17, 2020 [5] from the&#xA;perspective of RIPE RIS BGP collectors to study how and where such an&#xA;outage is visible, thereby contributing to the limited work in this&#xA;space. Our work further shows a BGP-based approach to study outages. The&#xA;goal of our work is to make a first step towards data-driven outage&#xA;monitoring systems for real-time outage detection.&lt;/p&gt;&#xA;&lt;h1 id=&#34;the-cloudflare-outage-in-2020&#34;&gt;The Cloudflare Outage in 2020&lt;/h1&gt;&#xA;&lt;p&gt;Cloudflare offers content delivery (CDN), Internet security (e.g., DDoS&#xA;mitigation), and domain name (DNS) services. On July 17, 2020 a&#xA;configuration error in their backbone network resulted in a major outage&#xA;that made hosted services unreachable [5], [6]. This outage resulted&#xA;in a 50% traffic drop for a duration of 27 minutes. The goal of our work&#xA;is to use this particular case to study the visibility of such kinds of&#xA;outages on the Internet’s control plane with the goal of enabling&#xA;data-driven outage detecting systems.&lt;/p&gt;&#xA;&lt;p&gt;We next summarize the incident timeline according to Cloudflare’s&#xA;reports [5], [6]. At &lt;strong&gt;20:25 UTC&lt;/strong&gt; the link between the datacenters&#xA;in Newark (EWR) and Chicago (ORD) started having issues due to unknown&#xA;reasons. This led to congestion of the link between Ashburn (IAD) and&#xA;Atlanta (ATL). To resolve the congestion, the configuration of ATL was&#xA;changed at &lt;strong&gt;21:12 UTC&lt;/strong&gt;. However, instead of removing traffic from ATL,&#xA;the faulty configuration lead to all routes being leaked into the&#xA;backbone, which made ATL attract more traffic and congest even faster.&#xA;At &lt;strong&gt;21:39 UTC&lt;/strong&gt; ATL was removed from the backbone completely which&#xA;immediately fixed the issue. At &lt;strong&gt;21:47 UTC&lt;/strong&gt; all customer facing&#xA;services were restored as the edge network operated normally again. Only&#xA;some internal services for logs and metrics were still impacted by&#xA;congestion, but these issues were resolved at &lt;strong&gt;22:10 UTC&lt;/strong&gt;.&lt;/p&gt;&#xA;&lt;h1 id=&#34;methodology&#34;&gt;Methodology&lt;/h1&gt;&#xA;&lt;p&gt;The goal of our study is to analyze the &lt;em&gt;visibility&lt;/em&gt; of a major&#xA;infrastructure outage on the Internet’s control plane (i.e., BGP). Our&#xA;analysis is thus based on archived BGP data publicly available from RIPE&#xA;RIS route collectors that provide an extensive amount of BGP data for&#xA;many years. &lt;em&gt;Collectors&lt;/em&gt; are special routers of which the only purpose&#xA;is to receive BGP update messages. They are located in several&#xA;countries, and are most commonly peering with many service providers&#xA;directly inside large Internet Exchange Points.&lt;/p&gt;&#xA;&lt;p&gt;To keep the noise of regular operation low, we focus on three RIPE RIS&#xA;collectors in proximity to the location of the outage: New York&#xA;(&lt;strong&gt;rrc11&lt;/strong&gt;), Palo Alto (&lt;strong&gt;rrc14&lt;/strong&gt;) and Miami (&lt;strong&gt;rrc16&lt;/strong&gt;). We focus our&#xA;analysis on two main attributes available in BGP update messages—an&#xA;approach to enable the study of further outages. Namely, the number of&#xA;IP prefix updates and the BGP communities contained in these messages.&#xA;During normal operation, a certain amount of prefix announcements and&#xA;withdraws are to be expected. Outages on the other hand are usually&#xA;rooted in hardware failures or misconfigurations, both of which can be&#xA;resolved by rerouting and steering traffic. Due to the time sensitivity&#xA;of critical outages it would come to no surprise to see a sudden surge&#xA;in prefix announcements or withdrawals being published by a network&#xA;experiencing such an outage. Secondly, community tags can be used to&#xA;direct and steer traffic by passing certain messages to routers. As&#xA;such, sudden changes in the number of announcements of certain&#xA;communities can be used as an quantitative indicator for active traffic&#xA;steering, without knowing the semantics of a certain community value. We&#xA;analyze Cloudflare’s outage by visualizing the changes of these&#xA;attributes over time in suitable plots. At the same time, we attempt to&#xA;correlate sudden changes in the plots with important timestamps&#xA;extracted from outage reports published by Cloudflare.&lt;/p&gt;&#xA;&lt;h1 id=&#34;visibility-of-the-outage-in-bgp&#34;&gt;Visibility of the Outage in BGP&lt;/h1&gt;&#xA;&lt;p&gt;We show the number of BGP update messages received by the three&#xA;collectors from AS 13335 (Cloudflare) in&#xA;Figure &lt;a href=&#34;#fig:cloudflare1707_updates_over_time&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;fig:cloudflare1707_updates_over_time&#34;&gt;1&lt;/a&gt;.&#xA;The solid blue line shows the number of BGP messages received during the&#xA;outage. The vertical grey bars indicate different events during the&#xA;outage described in&#xA;Section &lt;a href=&#34;#section:cloudflare_timeline&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;section:cloudflare_timeline&#34;&gt;2&lt;/a&gt;.&#xA;First, we notice an increase in BGP message at the time of the&#xA;misconfiguration at 21:12 UTC. This event is clearly visible at all&#xA;vantage points, though with differing intensity. To set a baseline, we&#xA;show the number of BGP updates received at the same time one week&#xA;earlier during normal operation (dashed orange line). It shows that the&#xA;numbers outside the outage roughly align with those from a week ago and&#xA;that the numbers during the outage are significantly higher. Thus,&#xA;unusual volumes of BGP updates indicate network configuration changes&#xA;(due to the outage).&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;embed src=&#34;figures/announcements.pdf.png&#34; id=&#34;fig:cloudflare1707_updates_over_time&#34; /&gt;&lt;figcaption aria-hidden=&#34;true&#34;&gt;Fig. 1: BGP updates by Cloudflare recorded on three collectors during the outage of 17.07.2020.&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;While deviations in the volume of announcements are one change&#xA;indicator, they do not reveal the semantic of a change. Routing policies&#xA;(e.g., biasing path, peer selection, or performing traffic engineering&#xA;in general) are typically realized by tagging BGP announcements with&#xA;communities. If we look at the communities distributed by Cloudflare&#xA;during the outage, we can attempt to infer some information about the&#xA;anatomy of the outage, e.g., affected colocations.&lt;/p&gt;&#xA;&lt;p&gt;Figure&#xA;&lt;a href=&#34;#fig:cloudflare1707_communities_histogram&#34; data-reference-type=&#34;ref&#34; data-reference=&#34;fig:cloudflare1707_communities_histogram&#34;&gt;2&lt;/a&gt;&#xA;shows a histogram of community values that were included in BGP updates&#xA;by Cloudflare between 20:00 UTC and 23:59 UTC. We consider only&#xA;community values that were announced more than 50 times across all three&#xA;collectors. Clearly, some community values are announced more often than&#xA;others. The number of announcements visible at &lt;strong&gt;rrc16&lt;/strong&gt; is about an&#xA;order of magnitude lower than in other collectors. To validate our&#xA;observations we contacted Cloudflare. They confirmed that they rely on&#xA;BGP communities for internal traffic management through the backbone, as&#xA;well as route visibility through internal route collection. They also&#xA;shared the mapping of some communities. Indeed, some of the observed BGP&#xA;communities, i.e., &lt;strong&gt;13335:10027&lt;/strong&gt; (&amp;ldquo;ATL01&amp;rdquo;), &lt;strong&gt;13335:19000&lt;/strong&gt;&#xA;(&amp;ldquo;NORTH-AMERICA&amp;rdquo;) and &lt;strong&gt;13335:20520&lt;/strong&gt; (&amp;ldquo;SITE-LOCAL-ROUTE&amp;rdquo;), were&#xA;announced due to the faulty configuration, as they do exactly match the&#xA;communities being added to the leaked routes.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;embed src=&#34;figures/communities.pdf.png&#34; id=&#34;fig:cloudflare1707_communities_histogram&#34; /&gt;&lt;figcaption aria-hidden=&#34;true&#34;&gt;Fig. 2: Histogram of community values announced by Cloudflare between 20:00–23:59 UTC on 17.07.2020.&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;h1 id=&#34;discussion-and-next-steps&#34;&gt;Discussion and Next Steps&lt;/h1&gt;&#xA;&lt;p&gt;Our analysis shows that the number of prefix updates is a simple but&#xA;noisy activity change detector. A surge in control plane activity&#xA;combined with BGP communities changes is an excellent indicator to infer&#xA;changes in the network when the semantics of these communities are&#xA;known. With these insights, we were able to infer that a route leak had&#xA;occurred not only into the network’s backbone but also to the Internet.&#xA;This was not mentioned in the incident report but later confirmed by&#xA;Cloudflare. Building a data-driven outage detector is a non-trivial task&#xA;given the noisy nature of control-plane messages. Yet, we posit that&#xA;combining different control plane signals can pave the way for building&#xA;such a detector as the next step in our work. We also plan to&#xA;investigate other outages, e.g., the Google outage [7] and Facebook&#xA;outage [8], as well as possible disruptions of popular applications&#xA;due to such outages [9].&lt;/p&gt;&#xA;&lt;h1 id=&#34;acknowledgements&#34;&gt;Acknowledgements&lt;/h1&gt;&#xA;&lt;div class=&#34;acks&#34;&gt;&#xA;&lt;p&gt;This work was partially funded by the ERC Starting Grant ResolutioNet&#xA;(679158) and BMBF BIFOLD 01IS18025A and 01IS18037A.&lt;/p&gt;&#xA;&lt;/div&gt;&#xA;&lt;h1 id=&#34;references&#34;&gt;References&lt;/h1&gt;&#xA;&lt;div id=&#34;refs&#34; class=&#34;references csl-bib-body&#34;&gt;&#xA;&lt;div id=&#34;ref-benson_gaining_2013&#34; class=&#34;csl-entry&#34;&gt;&#xA;&lt;p&gt;&lt;span class=&#34;csl-left-margin&#34;&gt;[1] &lt;/span&gt;&lt;span&#xA;class=&#34;csl-right-inline&#34;&gt;K. Benson, A. Dainotti, K. Claffy, and E. Aben,&#xA;“&lt;span class=&#34;nocase&#34;&gt;Gaining insight into AS-level outages through&#xA;analysis of Internet Background Radiation&lt;/span&gt;,” Apr. 2013.&lt;/span&gt;&lt;/p&gt;&#xA;&lt;/div&gt;&#xA;&lt;div id=&#34;ref-dainotti_analysis_2011&#34; class=&#34;csl-entry&#34;&gt;&#xA;&lt;p&gt;&lt;span class=&#34;csl-left-margin&#34;&gt;[2] &lt;/span&gt;&lt;span&#xA;class=&#34;csl-right-inline&#34;&gt;A. Dainotti &lt;em&gt;et al.&lt;/em&gt;, “&lt;span&#xA;class=&#34;nocase&#34;&gt;Analysis of Country-Wide Internet Outages Caused by&#xA;Censorship&lt;/span&gt;,” 2011.&lt;/span&gt;&lt;/p&gt;&#xA;&lt;/div&gt;&#xA;&lt;div id=&#34;ref-giotsas_detecting_2017&#34; class=&#34;csl-entry&#34;&gt;&#xA;&lt;p&gt;&lt;span class=&#34;csl-left-margin&#34;&gt;[3] &lt;/span&gt;&lt;span&#xA;class=&#34;csl-right-inline&#34;&gt;V. Giotsas, C. Dietzel, G. Smaragdakis, A.&#xA;Feldmann, A. Berger, and E. Aben, “Detecting Peering Infrastructure&#xA;Outages in the Wild,” Aug. 2017.&lt;/span&gt;&lt;/p&gt;&#xA;&lt;/div&gt;&#xA;&lt;div id=&#34;ref-tao_wan_analysis_2006&#34; class=&#34;csl-entry&#34;&gt;&#xA;&lt;p&gt;&lt;span class=&#34;csl-left-margin&#34;&gt;[4] &lt;/span&gt;&lt;span&#xA;class=&#34;csl-right-inline&#34;&gt;T. Wan and P. C. van Oorschot, “Analysis of BGP&#xA;prefix origins during Google’s May 2005 outage,” 2006.&lt;/span&gt;&lt;/p&gt;&#xA;&lt;/div&gt;&#xA;&lt;div id=&#34;ref-Cloudflare-outage-2020&#34; class=&#34;csl-entry&#34;&gt;&#xA;&lt;p&gt;&lt;span class=&#34;csl-left-margin&#34;&gt;[5] &lt;/span&gt;&lt;span&#xA;class=&#34;csl-right-inline&#34;&gt;Cloudflare, “&lt;span class=&#34;nocase&#34;&gt;Cloudflare&#xA;Network and Resolver Issues&lt;/span&gt;.”&#xA;&lt;a href=&#34;https://www.cloudflarestatus.com/incidents/b888fyhbygb8&#34;&gt;https://www.cloudflarestatus.com/incidents/b888fyhbygb8&lt;/a&gt;, 2020.&lt;/span&gt;&lt;/p&gt;&#xA;&lt;/div&gt;&#xA;&lt;div id=&#34;ref-graham-cumming_cloudflare_2020-1&#34; class=&#34;csl-entry&#34;&gt;&#xA;&lt;p&gt;&lt;span class=&#34;csl-left-margin&#34;&gt;[6] &lt;/span&gt;&lt;span&#xA;class=&#34;csl-right-inline&#34;&gt;J. Graham-Cumming, “Cloudflare outage on July&#xA;17, 2020.” Jul. 2020. Accessed: Jul. 29, 2020. [Online]. Available:&#xA;&lt;a href=&#34;https://blog.cloudflare.com/cloudflare-outage-on-july-17-2020/&#34;&gt;https://blog.cloudflare.com/cloudflare-outage-on-july-17-2020/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;&#xA;&lt;/div&gt;&#xA;&lt;div id=&#34;ref-Google-outage-2019&#34; class=&#34;csl-entry&#34;&gt;&#xA;&lt;p&gt;&lt;span class=&#34;csl-left-margin&#34;&gt;[7] &lt;/span&gt;&lt;span&#xA;class=&#34;csl-right-inline&#34;&gt;Google, “Google Cloud Networking Incident&#xA;#19009.”&#xA;&lt;a href=&#34;https://status.cloud.google.com/incident/cloud-networking/19009&#34;&gt;https://status.cloud.google.com/incident/cloud-networking/19009&lt;/a&gt;,&#xA;2019.&lt;/span&gt;&lt;/p&gt;&#xA;&lt;/div&gt;&#xA;&lt;div id=&#34;ref-FB-outage-2021&#34; class=&#34;csl-entry&#34;&gt;&#xA;&lt;p&gt;&lt;span class=&#34;csl-left-margin&#34;&gt;[8] &lt;/span&gt;&lt;span&#xA;class=&#34;csl-right-inline&#34;&gt;Facebook, “&lt;span class=&#34;nocase&#34;&gt;More details&#xA;about the October 4 outage&lt;/span&gt;.”&#xA;&lt;a href=&#34;https://engineering.fb.com/2021/10/05/networking-traffic/outage-details/&#34;&gt;https://engineering.fb.com/2021/10/05/networking-traffic/outage-details/&lt;/a&gt;,&#xA;2021.&lt;/span&gt;&lt;/p&gt;&#xA;&lt;/div&gt;&#xA;&lt;div id=&#34;ref-Analysis-dependencies-IMC2020&#34; class=&#34;csl-entry&#34;&gt;&#xA;&lt;p&gt;&lt;span class=&#34;csl-left-margin&#34;&gt;[9] &lt;/span&gt;&lt;span&#xA;class=&#34;csl-right-inline&#34;&gt;A. Kashaf, V. Sekar, and Y. Agarwal, “&lt;span&#xA;class=&#34;nocase&#34;&gt;Analyzing Third Party Service Dependencies in Modern Web&#xA;Services: Have We Learned from the Mirai-Dyn Incident?&lt;/span&gt;”&#xA;2020.&lt;/span&gt;&lt;/p&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;</description>
    </item>
    <item>
      <title>Building a lexer for lambda</title>
      <link>/posts/lambda-lexer/</link>
      <pubDate>Mon, 07 Dec 2020 11:30:00 +0000</pubDate>
      <guid>/posts/lambda-lexer/</guid>
      <description>&lt;p&gt;Recently I got quite interested in the &lt;a href=&#34;https://en.wikipedia.org/wiki/Lambda_calculus&#34;&gt;lambda calculus&lt;/a&gt;, more specifically its untyped variant.&#xA;The lambda calculus is a model of computation that is the base for many functional programming languages such as &lt;a href=&#34;https://www.haskell.org&#34;&gt;Haskell&lt;/a&gt; (which is typed) or &lt;a href=&#34;https://en.wikipedia.org/wiki/Scheme_(programming_language)&#34;&gt;Scheme&lt;/a&gt;.&#xA;For quite some time I also wanted to build an interpreter for a programming language so I decided to give it a try and build one for the lambda calculus.&lt;/p&gt;&#xA;&lt;p&gt;I will call this interpreter &lt;em&gt;lambda&lt;/em&gt;.&lt;/p&gt;&#xA;&lt;h1 id=&#34;overview&#34;&gt;Overview&lt;/h1&gt;&#xA;&lt;p&gt;Before we get started, let&amp;rsquo;s get a quick overview of how the interpreter for &lt;em&gt;lambda&lt;/em&gt; (and most interpreters for other languages, really) will be built.&#xA;It consists of three basic parts, a lexer, a parser and an evaluator, each of them serving a different purpose:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;The lexer&lt;/strong&gt; takes a program in form of an input string and turns it into a list of &lt;em&gt;tokens&lt;/em&gt; or &lt;em&gt;lexemes&lt;/em&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;The parser&lt;/strong&gt; takes these tokens and composes them into an &lt;em&gt;abstract syntax tree&lt;/em&gt; (or &amp;ldquo;AST&amp;rdquo;) according to the grammar of the language.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;The evaluator&lt;/strong&gt; finally takes the AST and evaluates its statements and expressions according to evaluation rules and yields some behaviour.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;This project is somewhat large and so my writing about it will will focus on the grammar and lexer of &lt;em&gt;lambda&lt;/em&gt;.&lt;/p&gt;&#xA;&lt;h1 id=&#34;the-grammar&#34;&gt;The Grammar&lt;/h1&gt;&#xA;&lt;p&gt;The grammar for the untyped Lambda calculus is fairly simple. Here it is in &lt;a href=&#34;https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form&#34;&gt;EBNF&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;variable    = &amp;#34;v&amp;#34; | variable, &amp;#34;&amp;#39;&amp;#34;;&#xA;application = term, &amp;#34; &amp;#34;, term;&#xA;abstraction = &amp;#34;λ&amp;#34;, letter, &amp;#34;.&amp;#34;, term;&#xA;term        = variable | &amp;#34;(&amp;#34;, application, &amp;#34;)&amp;#34; | &amp;#34;(&amp;#34;, abstraction, &amp;#34;)&amp;#34;;&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Examples for valid &lt;code&gt;terms&lt;/code&gt; (&amp;ldquo;lambda-terms&amp;rdquo;) based on this grammar are:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;v&#xA;(v v&amp;#39;)&#xA;(λv.v)&#xA;(λv.(v v&amp;#39;)&#xA;((λv.((λv&amp;#39;.(v&amp;#39; v&amp;#39;&amp;#39;)) v)) v&amp;#39;&amp;#39;&amp;#39;)&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This actually looks quite complicated. There are many parentheses and the variables are only distinguished by the number of &lt;code&gt;&#39;&lt;/code&gt; suffixes.&#xA;So let&amp;rsquo;s make some small changes to the lambda calculus&amp;rsquo; grammar for our convenience:&lt;/p&gt;&#xA;&lt;p&gt;First, we replace the variable rule by the following one:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;variable = &amp;#34;a&amp;#34; | &amp;#34;b&amp;#34; | ... | &amp;#34;z&amp;#34;;&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While the original rule technically allowed for an infinite number of variables, which is important to some proofs, such as &lt;a href=&#34;https://isabelle.in.tum.de/nominal/example.html&#34;&gt;Barendregt&amp;rsquo;s Substituion Lemma&lt;/a&gt;,&#xA;we very likely won&amp;rsquo;t run into issues limiting ourselves to just 26 variable names.&lt;/p&gt;&#xA;&lt;p&gt;Secondly, we add a rule to allow omitting the outermost parentheses:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;lambdaterm  = variable | application | abstraction | term;&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, we make a small change to the &lt;code&gt;abstraction&lt;/code&gt; rule:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;abstraction = &amp;#34;\&amp;#34;, letter, &amp;#34;.&amp;#34; , ( abstraction | term );&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is allows us to write nested abstractions more easily, without having to use many parentheses, for example &lt;code&gt;λx.λy.(x y)&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Finally, also substitute the &lt;code&gt;λ&lt;/code&gt; symbol for &lt;code&gt;\ &lt;/code&gt; to make it easier to type.&lt;/p&gt;&#xA;&lt;p&gt;As a result, our valid lambda-terms from before would now look like this:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;v&#xA;x y &#xA;\x.x&#xA;\x.(x y)&#xA;(x y) (x y)&#xA;\x.\y.(x y)&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Of course we can still use the parentheses if we want to.&lt;/p&gt;&#xA;&lt;p&gt;The final grammar now looks like this:&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;variable    = &amp;#34;a&amp;#34; | &amp;#34;b&amp;#34; | ... | &amp;#34;z&amp;#34;;&#xA;application = term, &amp;#34; &amp;#34;, term;&#xA;abstraction = &amp;#34;\&amp;#34;, letter, &amp;#34;.&amp;#34; , ( abstraction | term );&#xA;term        = variable | &amp;#34;(&amp;#34;, application, &amp;#34;)&amp;#34; | &amp;#34;(&amp;#34;, abstraction, &amp;#34;)&amp;#34;;&#xA;lambdaterm  = variable | application | abstraction | term;&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;h1 id=&#34;implementing-the-lexer&#34;&gt;Implementing the lexer&lt;/h1&gt;&#xA;&lt;p&gt;Now that we have the grammar settled, we can start implementing the lexer. It&amp;rsquo;s main purpose is to split the incoming stream of characters into so called &amp;ldquo;tokens&amp;rdquo; or &amp;ldquo;lexemes&amp;rdquo;.&#xA;These tokens will later be consumed by the parser to compose the Abstract Syntax Tree according to our grammar.&lt;/p&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s start by defining a Token structure and all the tokens we need:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Kind&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Token&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Kind&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;     &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Kind&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Literal&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Position&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;const&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ILLEGAL&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Kind&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;iota&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// Illegal token&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;EOF&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;                 &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// EOF&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;LAMBDA&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;              &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// &amp;#34;\&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;LPAREN&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;              &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// &amp;#34;(&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;RPAREN&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;              &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// &amp;#34;)&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;DOT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;                 &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// &amp;#34;.&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;IDENT&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;               &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// any ASCII Char&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;SPACE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;               &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// &amp;#34; &amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;Token&lt;/code&gt; struct will hold three things we need later: the &lt;code&gt;Kind&lt;/code&gt; of token we are dealing with, the Literal, that is the &amp;ldquo;value&amp;rdquo; of the token as it appears in the input stream,&#xA;and the position of a tokens first character.&#xA;We have a &lt;code&gt;Kind&lt;/code&gt; for each type of token that can appear according to our grammar. Notice how we do not differentiate between upper- and lower-case letters for identifiers yet.&#xA;&lt;code&gt;EOF&lt;/code&gt; and &lt;code&gt;ILLEGAL&lt;/code&gt; are special. They signify the end of the input stream as well as illegal characters (such as numeric digits).&lt;/p&gt;&#xA;&lt;p&gt;The lexer itself is pretty straightforward. It exposes a single method we call &lt;code&gt;Next()&lt;/code&gt; which emits whatever is the next token in the input. Internally, it will read byte-by-byte as much input as needed.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 5&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Lexer&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;input&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;   &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pos&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;     &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;readPos&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ch&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;      &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;byte&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;New&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;input&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Lexer&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Lexer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;input&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;input&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;readChar&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;Here we define our &lt;code&gt;Lexer&lt;/code&gt; which holds a copy of the input string, some information about the current and next read position and the current byte we are inspecting.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;readChar()&lt;/code&gt; is defined like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;18&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;21&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;22&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;23&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;24&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;25&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;26&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Lexer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;readChar&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;readPos&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;input&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ch&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;else&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ch&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;input&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;readPos&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pos&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;readPos&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;readPos&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;It reads exactly one byte of input and updates the internal state of the lexer accordingly.&lt;/p&gt;&#xA;&lt;p&gt;Finally, let&amp;rsquo;s have a look at &lt;code&gt;Next()&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;28&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;29&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;30&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;31&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;32&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;33&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;34&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;35&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;36&#xA;&lt;/span&gt;&lt;span class=&#34;hl&#34;&gt;&lt;span class=&#34;lnt&#34;&gt;37&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;38&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;39&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;40&#xA;&lt;/span&gt;&lt;span class=&#34;hl&#34;&gt;&lt;span class=&#34;lnt&#34;&gt;41&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;42&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;43&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;44&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;45&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;46&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;47&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;48&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;49&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;50&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;51&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Lexer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Next&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Token&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;var&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;tok&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Token&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;switch&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ch&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;case&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;sc&#34;&gt;&amp;#39;\\&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;tok&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;newToken&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;LAMBDA&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ch&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pos&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// [...]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;case&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;sc&#34;&gt;&amp;#39;.&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;tok&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;newToken&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;DOT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ch&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pos&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line hl&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;case&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;tok&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Literal&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;tok&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Kind&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;EOF&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;tok&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Position&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pos&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line hl&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;default&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;isLetter&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ch&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;tok&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;newToken&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;IDENT&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ch&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pos&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;else&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;tok&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;newToken&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ILLEGAL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ch&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pos&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;l&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;readChar&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;tok&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;It is basically just a &lt;code&gt;switch&lt;/code&gt; statement that returns the correct kind of token based on the current char under inspection. There are two special cases here:&lt;/p&gt;&#xA;&lt;p&gt;On line 37 we check for the zero byte &lt;code&gt;0&lt;/code&gt;. This is being set by &lt;code&gt;readChar()&lt;/code&gt; once we have reached the end of the input. Accordingly, the token to return is &lt;code&gt;token.EOF&lt;/code&gt;.&#xA;Secondly, on line 40 we use the defalt cause to assert whether &lt;code&gt;l.ch&lt;/code&gt; (the current byte) is actually a letter we want to allow. If that is the case we can plug the byte right into&#xA;the token Literal. This is possible because according to our grammar, all identifiers for variables have to be a single character. Other lexers usually have a function called &lt;code&gt;consumeBytes()&lt;/code&gt; or similar which will read a sequence of bytes that qualify as a single identifier and return them for the token literal.&#xA;In case our byte is not a valid (lowercase) character, we return &lt;code&gt;token.ILLEGAL&lt;/code&gt; as we have checked all other valid cases already.&lt;/p&gt;&#xA;&lt;p&gt;For completeness, here are the definitions of &lt;code&gt;newToken&lt;/code&gt; and &lt;code&gt;isLetter&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;58&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;59&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;60&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;61&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;62&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;63&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;64&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;newToken&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;kind&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Kind&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ch&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;byte&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Token&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Token&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Kind&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;kind&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Literal&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ch&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Position&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;isLetter&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ch&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;byte&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;bool&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;sc&#34;&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ch&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ch&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;sc&#34;&gt;&amp;#39;z&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h1&gt;&#xA;&lt;p&gt;And that is already it for this part of the interpreter. In future we will explore the parser which takes the tokens emitted by the lexer and compose them into the Abstract Syntax Tree.&#xA;You can find all the code shown here in the &lt;a href=&#34;https://github.com/ldb/lambda&#34;&gt;lambda GitHub repository&lt;/a&gt;.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Building a Spotify scraper</title>
      <link>/posts/spotify-scraper/</link>
      <pubDate>Sat, 31 Oct 2020 11:30:00 +0000</pubDate>
      <guid>/posts/spotify-scraper/</guid>
      <description>&lt;p&gt;Recently I came across &lt;a href=&#34;https://spotifycharts.com&#34;&gt;spotifychart.com&lt;/a&gt;, an official website where Spotify has been publishing daily and weekly&#xA;listening trends since 2017 for several regions, including global.&lt;/p&gt;&#xA;&lt;p&gt;Seeing that I asked myself how social media apps that rely heavily on music (think Vine, and more recently TikTok)&#xA;influence these charts. Especially in the case of TikTok, some musicians careers seem to have been kickstarted by this platform. Naturally&#xA;I asked myself whether this would reflect also in the numbers, and so I decided to run a small explorative project analyzing Spotify Charts.&lt;/p&gt;&#xA;&lt;p&gt;In this post I&amp;rsquo;ll describe how I wrote a scraper to download these charts so we can analyze them offline.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-api&#34;&gt;The API&lt;/h2&gt;&#xA;&lt;p&gt;Even though Spotify does not provide an official public API to access these charts, it was quite clear that downloading all charts&#xA;should not be too hard. There is a link on the website to download charts for a given date and region as a CSV file. Lookin at the&#xA;website&amp;rsquo;s source code we can see what URL is called when clicking that link:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;a&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;href&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;/regional/global/daily/2020-10-30/download&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;class&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;header-csv&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;download&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;Download to CSV&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can see, that both region (&lt;code&gt;global&lt;/code&gt;) and date are part of the URL. Scripting this should not be hard.&#xA;From the dropdown menu we can see, that charts are available for 66 regions at this moment, starting at January 2017.&#xA;In total we will end up with about 92k CSVs.&#xA;That&amp;rsquo;s a lot of files to download, and doing that sequentially is definitely a not an option. Fortunately Go, my favourite programming language,&#xA;makes it very easy to design with concurrency in mind.&lt;/p&gt;&#xA;&lt;h2 id=&#34;concurrency-first-design&#34;&gt;Concurrency First Design&lt;/h2&gt;&#xA;&lt;p&gt;The idea to this design is to have a number of workers download whole regions concurrently. Each of those workers in turn spawns new workers&#xA;which download one day of charts for the region each.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;40&#xA;&lt;/span&gt;&lt;span class=&#34;hl&#34;&gt;&lt;span class=&#34;lnt&#34;&gt;41&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;42&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;43&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;44&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;45&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;46&#xA;&lt;/span&gt;&lt;span class=&#34;hl&#34;&gt;&lt;span class=&#34;lnt&#34;&gt;47&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;48&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;49&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;50&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;51&#xA;&lt;/span&gt;&lt;span class=&#34;hl&#34;&gt;&lt;span class=&#34;lnt&#34;&gt;52&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;53&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;54&#xA;&lt;/span&gt;&lt;span class=&#34;hl&#34;&gt;&lt;span class=&#34;lnt&#34;&gt;55&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;56&#xA;&lt;/span&gt;&lt;span class=&#34;hl&#34;&gt;&lt;span class=&#34;lnt&#34;&gt;57&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;58&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;59&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;60&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;61&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;62&#xA;&lt;/span&gt;&lt;span class=&#34;hl&#34;&gt;&lt;span class=&#34;lnt&#34;&gt;63&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;64&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;65&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line hl&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;maxRegions&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;make&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;chan&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{},&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;wg&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;sync&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;WaitGroup&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;wg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Add&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;dates&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;*&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;len&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;spotify&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Regions&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;regCode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;regName&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;range&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;spotify&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Regions&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;regCode&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;regCode&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;regName&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;regName&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line hl&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;maxRegions&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{}{}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// Each worker spawned here will download one region ...&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;go&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;defer&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line hl&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;maxRegions&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line hl&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;maxConcurrency&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;make&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;chan&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{},&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;50&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;for&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;_&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;date&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;range&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;dates&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line hl&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;maxConcurrency&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;struct&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{}{}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// ... using new workers that each download one day.&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;go&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;date&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;                &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;defer&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;                    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;wg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Done&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line hl&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;                    &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;maxConcurrency&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;                &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;In this listing we can see the code that is responsible for spawning the workers. It is relatively straightforward.&#xA;First, we range over all regions known (defined in a map called &lt;code&gt;spotify.Regions&lt;/code&gt;) and spawn a worker for each region using a goroutine.&#xA;Secondly, for each day, each worker will spawn another goroutine.&lt;/p&gt;&#xA;&lt;p&gt;In Go, the &lt;code&gt;go&lt;/code&gt; keyword before a function will execute the function asynchronously. This means that we can iterate through the loop very&#xA;quickly, because all workers now run in the background. However, the Go runtime does not wait for goroutines to finish, so use a&#xA;&lt;code&gt;sync.WaitGroup&lt;/code&gt;. For each goroutine we spawn, we increase the counter (line 43) and decrease it for each goroutine that finishes (line 62).&#xA;In the end we call &lt;code&gt;wg.Wait()&lt;/code&gt;, which blocks until all goroutines called &lt;code&gt;wg.Done()&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;What we want to keep in mind, that we want to limit the level of concurrency. Spawning a big number of goroutines is not a big problem by&#xA;itself, we want to remind ourselves, that we are scraping data from someone else&amp;rsquo;s servers and don&amp;rsquo;t want to overload them with 92k&#xA;concurrent requests. Realistically, we would probably first run into ratelimits or starve our own internet connection before overloading&#xA;Spotify, but it&amp;rsquo;s still a good practicce.&lt;/p&gt;&#xA;&lt;p&gt;In order to limit the number of concurrent goroutines spawned, I use buffered channels which act as semaphores.&#xA;For each goroutine we create, we push a &lt;code&gt;struct{}&lt;/code&gt; into the channel (lines 47, 57). Once the channel has reached it&amp;rsquo;s size limit, this&#xA;operation becomes blocking until another goroutine removes a struct from the channel (lines 52, 63).&#xA;This pattern ensures that we never have more than 500 workers running at any time.&lt;/p&gt;&#xA;&lt;h2 id=&#34;saving-the-files&#34;&gt;Saving the files&lt;/h2&gt;&#xA;&lt;p&gt;The heart of the scraper runs inside the workers: the function that actually downloads the CSV and writes it to a file.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;&#xA;&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 76&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 77&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 78&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 79&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 80&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 81&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 82&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 83&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 84&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 85&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 86&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 87&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 88&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 89&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 90&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 91&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 92&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 93&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 94&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 95&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 96&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 97&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 98&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 99&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;100&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;101&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;102&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;103&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;104&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;105&#xA;&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;106&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td class=&#34;lntd&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;error&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;url&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;fmt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Sprintf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;%s/regional/%s/daily/%s/download&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;baseURL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;regCode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;date&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;http&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;url&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;defer&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Body&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Close&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;StatusCode&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;http&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;StatusNotFound&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;// If the file is not there, there is no point in retrying.&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;stop&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;errors&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;New&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;not found&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;h&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Header&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;h&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;text/csv;charset=UTF-8&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;errors&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;New&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;non CSV data&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;StatusCode&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;http&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;StatusOK&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;errors&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;New&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;non 200 code&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Join&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;dataPath&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;fmt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Sprintf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;%s/%s&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;regCode&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;date&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;os&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Create&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;defer&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Close&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;_&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;io&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Copy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Body&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;err&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;The code here is very straightforward. We download the CSV file, perform a couple of checks and write it into a file, by copying the buffer.&#xA;A hard lessen to learn was to check the HTTP Response Content-Type Headers to make sure we are actually downloading CSV content.&#xA;Before adding this, I accidentally downloaded the HTML representation for these from time to time, which quickly used up several GB of my disk.&lt;/p&gt;&#xA;&lt;h2 id=&#34;running-it&#34;&gt;Running it&lt;/h2&gt;&#xA;&lt;p&gt;With all the important parts pieced together and a bit of extra code for some visual progress we can now run the program:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2020/11/01 17:52:00 &lt;span class=&#34;m&#34;&gt;1398&lt;/span&gt; days, &lt;span class=&#34;m&#34;&gt;66&lt;/span&gt; regions&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Peru                 &lt;span class=&#34;m&#34;&gt;76&lt;/span&gt; / &lt;span class=&#34;m&#34;&gt;1398&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&amp;gt;----------------&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;   9m14s   &lt;span class=&#34;m&#34;&gt;5&lt;/span&gt; %&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Czech Republic      &lt;span class=&#34;m&#34;&gt;242&lt;/span&gt; / &lt;span class=&#34;m&#34;&gt;1398&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[==&lt;/span&gt;&amp;gt;--------------&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;   2m32s  &lt;span class=&#34;m&#34;&gt;17&lt;/span&gt; %&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Indonesia           &lt;span class=&#34;m&#34;&gt;201&lt;/span&gt; / &lt;span class=&#34;m&#34;&gt;1398&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[=&lt;/span&gt;&amp;gt;---------------&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;    3m3s  &lt;span class=&#34;m&#34;&gt;14&lt;/span&gt; %&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Chile               &lt;span class=&#34;m&#34;&gt;182&lt;/span&gt; / &lt;span class=&#34;m&#34;&gt;1398&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[=&lt;/span&gt;&amp;gt;---------------&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;   3m26s  &lt;span class=&#34;m&#34;&gt;13&lt;/span&gt; %&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Slovakia            &lt;span class=&#34;m&#34;&gt;125&lt;/span&gt; / &lt;span class=&#34;m&#34;&gt;1398&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[=&lt;/span&gt;&amp;gt;---------------&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;   4m33s   &lt;span class=&#34;m&#34;&gt;9&lt;/span&gt; %&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Thailand            &lt;span class=&#34;m&#34;&gt;387&lt;/span&gt; / &lt;span class=&#34;m&#34;&gt;1398&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[====&lt;/span&gt;&amp;gt;------------&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;    1m7s  &lt;span class=&#34;m&#34;&gt;28&lt;/span&gt; %&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Estonia             &lt;span class=&#34;m&#34;&gt;144&lt;/span&gt; / &lt;span class=&#34;m&#34;&gt;1398&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[=&lt;/span&gt;&amp;gt;---------------&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;   3m36s  &lt;span class=&#34;m&#34;&gt;10&lt;/span&gt; %&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Turkey              &lt;span class=&#34;m&#34;&gt;132&lt;/span&gt; / &lt;span class=&#34;m&#34;&gt;1398&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[=&lt;/span&gt;&amp;gt;---------------&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;   3m58s   &lt;span class=&#34;m&#34;&gt;9&lt;/span&gt; %&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Mexico               &lt;span class=&#34;m&#34;&gt;67&lt;/span&gt; / &lt;span class=&#34;m&#34;&gt;1398&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;&amp;gt;----------------&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;   7m53s   &lt;span class=&#34;m&#34;&gt;5&lt;/span&gt; %&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Hong Kong            &lt;span class=&#34;m&#34;&gt;38&lt;/span&gt; / &lt;span class=&#34;m&#34;&gt;1398&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt;-----------------&lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;  13m38s   &lt;span class=&#34;m&#34;&gt;3&lt;/span&gt; %&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It downloads all CSVs into a directory called &lt;code&gt;data&lt;/code&gt;, separated by region within a couple of minutes.&lt;/p&gt;&#xA;&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;&#xA;&lt;p&gt;The whole codebase is available on &lt;a href=&#34;https://github.com/ldb/spotify-charts-scraper&#34;&gt;Github&lt;/a&gt;. Feel free to check it out and contribute.&lt;/p&gt;&#xA;&lt;p&gt;I am by no means a data scientist, but I think this dataset is very interesting.&#xA;Maybe someone with a background in datascience can use it to produce some cool visualizations for &lt;a href=&#34;https://reddit.com/r/dataisbeautiful&#34;&gt;r/dataisbeatiful&lt;/a&gt;.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Lambda Interpreter</title>
      <link>/lambda/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/lambda/</guid>
      <description>&lt;p&gt;This is an implementation of &lt;a href=&#34;https://github.com/ldb/lambda&#34;&gt;lambda&lt;/a&gt;, a tiny lambda calculus interpreter, using &lt;a href=&#34;https://webassembly.org&#34;&gt;WebAssembly&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;lambda-calculus-syntax&#34;&gt;Lambda Calculus syntax:&lt;/h2&gt;&#xA;&lt;p&gt;The three basic rules are easy:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;(\x.x)&lt;/code&gt; for abstractions&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;(x y)&lt;/code&gt; for application&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;x&lt;/code&gt; for variables&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Additionally, parentheses can be omitted at outermost positions (e.g. &lt;code&gt;\x.(x x) y&lt;/code&gt;) and in abstraction bodies if the body is an abstraction (e.g &lt;code&gt;\x.\y.(x y)&lt;/code&gt;).&lt;/p&gt;&#xA;&lt;h2 id=&#34;try-it-out&#34;&gt;Try it out:&lt;/h2&gt;&#xA;&lt;p&gt;You can use Arrow Up / Down to scroll through your history of terms, ^L to clear the screen and create a permalink of the last term executed.&lt;/p&gt;&#xA;&#xA;&#xA;&lt;div&gt;&#xA;    &lt;style type=&#34;text/css&#34; scoped&gt;&#xA;        .light, .light input, .light button { background-color: #fff; color: #252525; }&#xA;        .dark,  .dark  input, .dark button { color: #fff; }&#xA;        input { flex-grow: 1; outline: none; border: 1px; width: 70%; }&#xA;        pre { font: 16px monospace; }&#xA;        input, button { font: 16px monospace; border: 1px solid; }&#xA;        button { float: right; border: 0px solid; text-decoration: underline}&#xA;        input:focus { box-shadow: 0px 0px .2em #6262fd; }&#xA;        h { font: 24px monospace; }&#xA;        #terminal { font: 16px monospace; overflow: auto; word-wrap: break-word; width: 100%; height: 450px; margin: 0 auto; padding: 2px; }&#xA;    &lt;/style&gt;&#xA;    &lt;div id=&#34;terminal&#34; class=&#34;&#34; style=&#34;border-radius: 10px;&#34;&gt;&#xA;        &lt;pre id=&#34;output&#34;&gt;&lt;/pre&gt;&#xA;        &lt;input onblur=&#34;this.focus()&#34; id=&#34;input&#34;&gt;&#xA;        &lt;button class=&#34;permalink&#34;&gt;Permalink&lt;/button&gt;&#xA;    &lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;script src=&#34;wasm_exec.js&#34;&gt;&lt;/script&gt;&#xA;&lt;script src=&#34;term.js&#34;&gt;&lt;/script&gt;&#xA;&#xA;</description>
    </item>
  </channel>
</rss>