<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[qryn: polyglot monitoring and observability]]></title><description><![CDATA[qryn is a fast, thin, all-in-one polyglot observability stack built on top of ClickHouse
Logs, Metrics and Traces made simple, right out of the box - batteries included! 🔋]]></description><link>https://blog.gigapipe.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1736852985560/280c1544-aa3b-4937-9011-781a7eba7b56.png</url><title>qryn: polyglot monitoring and observability</title><link>https://blog.gigapipe.com</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 08 Apr 2026 10:31:55 GMT</lastBuildDate><atom:link href="https://blog.gigapipe.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Tail Sampling with Otel + Gigapipe]]></title><description><![CDATA[In modern observability, capturing and storing every trace can quickly become impractical due to storage costs and noise from less relevant data. Tail sampling is a powerful technique that enables smarter trace retention by evaluating full traces bef...]]></description><link>https://blog.gigapipe.com/tail-sampling-with-otel-and-qryn</link><guid isPermaLink="true">https://blog.gigapipe.com/tail-sampling-with-otel-and-qryn</guid><category><![CDATA[tail sampling]]></category><category><![CDATA[otel-collector]]></category><category><![CDATA[gigapipe]]></category><category><![CDATA[observability]]></category><category><![CDATA[distributed tracing]]></category><category><![CDATA[OpenTelemetry]]></category><dc:creator><![CDATA[Alex Maitland]]></dc:creator><pubDate>Tue, 04 Feb 2025 21:56:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1738705895366/f1355b43-9c0e-427b-a4e4-d0a1371a268d.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738705678305/77819822-a5ac-4c90-abf3-fe8bb7b85893.gif" alt class="image--center mx-auto" /></p>
<p>In modern observability, capturing and storing every trace can quickly become impractical due to storage costs and noise from less relevant data. <strong>Tail sampling</strong> is a powerful technique that enables smarter trace retention by evaluating full traces before deciding whether to keep them. Let’s see how we can leverage this in combination the gigapipe polyglot stack.</p>
<h2 id="heading-what-is-tail-sampling">What is Tail Sampling?</h2>
<p>Unlike head-based sampling, which makes decisions at the start of a trace, tail sampling occurs after a trace is completed. This approach provides richer context, ensuring important traces—such as slow requests, errors, or specific customer interactions—are retained for analysis. The OpenTelemetry Collector supports tail sampling through its <code>tailsamplingprocessor</code>, allowing for advanced filtering and retention policies.</p>
<h2 id="heading-leveraging-qrynhttpsgigapipecom-as-an-opentelemetry-receiver">Leveraging <a target="_blank" href="https://gigapipe.com">qryn</a> as an OpenTelemetry Receiver</h2>
<p><a target="_blank" href="https://gigapipe.com">qryn</a> is a high-performance observability backend, acts as a native OpenTelemetry receiver, ingesting traces, logs, and metrics offering native LogQL, PromQL and Tempo compatibility.</p>
<p>By integrating the OpenTelemetry Collector with <a target="_blank" href="https://gigapipe.com">qryn</a>, organizations can benefit from a seamless pipeline where tail sampling decisions are made before storing data in <a target="_blank" href="https://gigapipe.com">qryn</a>. This setup optimizes both storage efficiency and query performance.</p>
<h3 id="heading-configuring-tail-sampling-with-qryn">Configuring Tail Sampling with qryn</h3>
<p>To enable tail sampling with qryn and OpenTelemetry, follow these key steps:</p>
<ol>
<li><p><strong>Deploy OpenTelemetry Collector</strong> – Ensure your collector is set up to receive traces from applications and forward them to qryn.</p>
</li>
<li><p><strong>Enable the Tail Sampling Processor</strong> – Define sampling rules in your <code>otel-collector-config.yaml</code>, such as retaining traces based on status codes, duration, or custom attributes.</p>
</li>
<li><p><strong>Export to qryn</strong> – Configure the collector to send selected traces to qryn’s OpenTelemetry-compatible API.</p>
</li>
</ol>
<h4 id="heading-example-opentelemetry-collector-configuration">Example OpenTelemetry Collector Configuration:</h4>
<pre><code class="lang-yaml"><span class="hljs-attr">receivers:</span>
  <span class="hljs-attr">otlp:</span>
    <span class="hljs-attr">protocols:</span>
      <span class="hljs-attr">grpc:</span>
      <span class="hljs-attr">http:</span>

<span class="hljs-attr">processors:</span>
  <span class="hljs-attr">tailsampling:</span>
    <span class="hljs-attr">decision_wait:</span> <span class="hljs-string">10s</span>
    <span class="hljs-attr">policies:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">error_traces</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">status_code</span>
        <span class="hljs-attr">status_code:</span>
          <span class="hljs-attr">status_codes:</span> [<span class="hljs-string">ERROR</span>]
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">long_traces</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">latency</span>
        <span class="hljs-attr">latency:</span>
          <span class="hljs-attr">threshold_ms:</span> <span class="hljs-number">1000</span>

<span class="hljs-attr">exporters:</span>
  <span class="hljs-attr">otlphttp:</span>
    <span class="hljs-attr">endpoint:</span> <span class="hljs-string">"http://qryn-gigapipe/api/v1/traces"</span>

<span class="hljs-attr">service:</span>
  <span class="hljs-attr">pipelines:</span>
    <span class="hljs-attr">traces:</span>
      <span class="hljs-attr">receivers:</span> [<span class="hljs-string">otlp</span>]
      <span class="hljs-attr">processors:</span> [<span class="hljs-string">tailsampling</span>]
      <span class="hljs-attr">exporters:</span> [<span class="hljs-string">otlphttp</span>]
</code></pre>
<h2 id="heading-benefits-of-tail-sampling">Benefits of Tail Sampling</h2>
<ul>
<li><p><strong>Reduced Storage Costs</strong> – By retaining only high-value traces, organizations can significantly cut down on observability storage expenses.</p>
</li>
<li><p><strong>Improved Query Performance</strong> – Less noise in the dataset leads to faster and more meaningful trace analysis.</p>
</li>
<li><p><strong>Enhanced Decision-Making</strong> – Tail sampling enables intelligent data retention, keeping critical issues and performance bottlenecks visible.</p>
</li>
</ul>
<h2 id="heading-its-that-simple">It’s that simple.</h2>
<p>By combining OpenTelemetry’s tail sampling capabilities with qryn’s scalable and efficient backend, teams can fine-tune their observability pipelines for optimal performance and cost-effectiveness. Implementing tail sampling ensures that only the most relevant traces are retained, enabling deeper insights and better troubleshooting without unnecessary data overload.</p>
<p><strong>Sign up for a free trial account at</strong> <a target="_blank" href="https://gigapipe.com"><strong>Gigapipe</strong></a><strong>.</strong> Bring your own OTEL Logs, Metrics and Traces to enjoy our truly polyglot observability platform.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://gigapipe.com/">https://gigapipe.com/</a></div>
<p> </p>
<p><a target="_blank" href="https://gigapipe.com/">https://gigapipe.com/</a></p>
]]></content:encoded></item><item><title><![CDATA[The Hidden Costs of Cloud Observability: Why Gigapipe Stands Out]]></title><description><![CDATA[As observability becomes a cornerstone of modern infrastructure, organizations are turning to platforms like Grafana Cloud and Datadog to monitor logs, metrics, and traces. However, these tools often come with hidden complexities, not just in their f...]]></description><link>https://blog.gigapipe.com/the-hidden-costs-of-cloud-observability-why-gigapipe-stands-out</link><guid isPermaLink="true">https://blog.gigapipe.com/the-hidden-costs-of-cloud-observability-why-gigapipe-stands-out</guid><category><![CDATA[Open Source]]></category><category><![CDATA[observability]]></category><category><![CDATA[Grafana]]></category><category><![CDATA[Datadog]]></category><category><![CDATA[Logs]]></category><category><![CDATA[metrics]]></category><category><![CDATA[traces]]></category><category><![CDATA[cloudcostmanagement]]></category><dc:creator><![CDATA[Alex Maitland]]></dc:creator><pubDate>Mon, 20 Jan 2025 08:22:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736955517297/ef0d2c99-54ef-429a-aa28-b37bd9ebd93c.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As observability becomes a cornerstone of modern infrastructure, organizations are turning to platforms like Grafana Cloud and Datadog to monitor logs, metrics, and traces. However, these tools often come with hidden complexities, not just in their functionality but also in their pricing models. At Gigapipe, we’ve built an alternative that prioritizes simplicity and affordability, ensuring that observability is accessible to all teams without breaking the bank.</p>
<p>Vantage recently released a detailed article comparing the complex costing models of both Datadog and Grafana cloud: <a target="_blank" href="https://www.vantage.sh/blog/datadog-vs-grafana-cost">https://www.vantage.sh/blog/datadog-vs-grafana-cost</a></p>
<p>It highlights the pitfalls many companies fall into with the ‘cheap entry’ or ‘free credits’ offerings which can end up vendor locking you with spiralling costs.</p>
<h2 id="heading-the-challenges-of-cloud-observability"><strong>The Challenges of Cloud Observability</strong></h2>
<p>Cloud observability platforms promise seamless insights into your systems, but the reality is often more complicated. Here are the three primary challenges we see in the industry:</p>
<p><strong>1. Overwhelming Complexity</strong></p>
<p>Many observability tools are packed with features, but this often leads to steep learning curves or more often than not, a system you end up using only a fraction of, but paying for all of. Configuring alerts, managing dashboards, and integrating with existing tools can become a daunting task, especially for smaller teams.</p>
<p><strong>2. Performance Trade-offs</strong></p>
<p>Running observability platforms in the cloud means relying on their infrastructure. This can lead to performance bottlenecks, especially during high-traffic periods or when handling large datasets. For example, in Grafana Cloud, users often report delays in rendering dashboards or querying data during peak usage as you cannot control the power of the infrastructure you’re using.</p>
<p><strong>3. Opaque Pricing Models</strong></p>
<p>The most significant pain point is pricing. Platforms like Datadog and Grafana Cloud often employ complex, usage-based pricing models that make it hard to predict costs. The Vantage blog post highlights how these models can spiral out of control, especially for growing businesses. From per-user fees to charges based on data ingestion, storage, and query volume, the pricing structure often feels more like a labyrinth than a transparent system.</p>
<h2 id="heading-the-gigapipe-difference"><strong>The Gigapipe Difference</strong></h2>
<p>At Gigapipe, we’ve reimagined cloud observability with a focus on simplicity and fairness. Here’s how we stand out:</p>
<p><strong>1. Simple, Transparent Pricing</strong></p>
<p>Our pricing model is straightforward: one flat rate based on your data volume. No hidden fees, no surprises. Whether you’re a Startup monitoring a few services or an enterprise handling terabytes of data, you’ll know exactly what you’re paying. Check out our pricing page for details: <a target="_blank" href="https://gigapipe.com/pricing.html">Gigapipe Pricing</a>.</p>
<p><strong>2. Superior Performance</strong></p>
<p>Gigapipe’s architecture leverages its own open-source observability suite (think of it as if Grafana combined all their different tools and correlated it all for you in a single database), ensuring high-speed data processing and real-time insights. By optimizing our platform for performance, we’ve eliminated many of the latency issues users face with other cloud solutions.</p>
<p><strong>3. User-Friendly Design</strong></p>
<p>We’ve built Gigapipe to be intuitive, with streamlined dashboards and easy-to-configure alerts. This means less time spent on setup and troubleshooting and more time focusing on your core business.</p>
<h2 id="heading-comparing-pricing-gigapipe-vs-the-alternatives"><strong>Comparing Pricing: Gigapipe vs. the alternatives</strong></h2>
<p>Let’s take a closer look at how Gigapipe compares to other platforms:</p>
<ul>
<li><p><strong>Grafana Cloud</strong>: While Grafana Cloud offers a free tier, its paid plans quickly become expensive as you scale. The pricing is based on ingestion rates, retention periods, and additional features like alerting and user management. Predicting your monthly bill can feel like solving a puzzle.</p>
</li>
<li><p><strong>Datadog</strong>: Known for its robust features, Datadog’s pricing model is even more intricate. With separate charges for infrastructure monitoring, log management, APM, and more, businesses often find themselves paying far more than expected.</p>
</li>
<li><p><strong>Gigapipe</strong>: In contrast, Gigapipe offers a single, <a target="_blank" href="https://gigapipe.com/pricing.html">predictable rate</a>. No matter how much you scale, you’ll always know your costs upfront. This makes budgeting easier and removes the stress of surprise bills.</p>
<ul>
<li><p>€149/month: 32gb RAM, 8 vCPUs, 1TB data storage</p>
</li>
<li><p>€249/month: 48GB RAM, 10 vCPUs, 2TB data storage</p>
</li>
<li><p>…</p>
</li>
<li><p>€1449/month: 192GB RAM, 48 vCPUs, 10TB data storage</p>
</li>
</ul>
</li>
</ul>
<p>To make the best direct comparison possible, we’ll use Vantage’s example for Logs. Now this example is quite heavily biased towards Grafana over Datadog already, making Grafana look infinitely more appealing by using this specific case where the indexing in Datadog puts its monthly rate over the $65k mark.</p>
<p>Their example also doesn’t account for queries and compute, which would increase the cost further especially if it’s a read-heavy setup. However here are their proposed costings for each (quoted from the article):</p>
<h4 id="heading-pricing-scenario-3-logs"><strong>Pricing Scenario #3: Logs</strong></h4>
<p>25,000 GB of log data is ingested and stored for 1 month.</p>
<p><strong>Grafana Cloud:</strong> 25,000 GB x $0.50 per GB = <strong>$12,500 total Grafana Cloud</strong></p>
<p><strong>Datadog:</strong></p>
<p>25,000 GB x $0.50 per GB = $12,500 total Grafana Cloud</p>
<p>25,000 GB x $0.10 per GB = $2,500 for ingestion</p>
<p>25,000 GB / 1 KB (assuming average log event size is 1 KB) = 25 billion log events</p>
<p>25 billion log events x ($2.50 / 1 million log events) = $62,500 for indexing</p>
<p>$2,500 for ingestion + $62,500 for indexing = $65,000 total Datadog $2,500 for ingestion + $62,500 for indexing = <strong>$65,000 total Datadog</strong></p>
<p><strong>Gigapipe:</strong></p>
<p>However, to put it into perspective for 25 billion logs equalling 25TB for the month, here’s what a sensible breakdown in Gigapipe would look like if we take into account the likely querying requirements of a dataset of this size:</p>
<ul>
<li><p>16 servers (on our scale-up plan €249/month) = €3984/month</p>
<ul>
<li><p>Per machine:</p>
<ul>
<li><p>48GB RAM</p>
</li>
<li><p>10 vCPUs</p>
</li>
</ul>
</li>
<li><p>Total storage: 32TB uncompressed</p>
</li>
</ul>
</li>
</ul>
<p><strong>Total monthly cost under Gigapipe: €3984/month or $4101/month</strong></p>
<p>Gigapipe aims to provide the tools and infrastructure growing companies require, in a clear and easily forecastable way. Infinite scalability with a clear cost vs performance structure that will REMAIN cost effective as client’s grow.</p>
<h2 id="heading-why-simplicity-matters"><strong>Why Simplicity Matters</strong></h2>
<p>Complex pricing models don’t just hurt your wallet, they also waste valuable time. Teams end up spending hours trying to optimize their usage to fit within budget constraints. At Gigapipe, we believe that observability should empower teams, not burden them. By keeping our pricing simple and our setup fast, we let you focus on what matters: building and maintaining great systems.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Cloud observability is essential, but it shouldn’t come with unnecessary complexity or unpredictable costs. Gigapipe offers a streamlined alternative that combines powerful features with straightforward pricing. If you’re tired of navigating convoluted pricing models and dealing with performance issues, it’s time to give Gigapipe a try.</p>
<p>Visit <a target="_blank" href="https://gigapipe.com/">Gigapipe</a> today to learn more and see how we can transform your observability experience.</p>
]]></content:encoded></item><item><title><![CDATA[🐤 Merging Parquet with chsql + duckdb]]></title><description><![CDATA[If you’re familiar with our blog, you already know about our DuckDB Community Extension chsql providing a growing number of ClickHouse SQL macros and functions for DuckDB users. You can install chsql right from DuckDB SQL:
INSTALL chsql FROM communit...]]></description><link>https://blog.gigapipe.com/merging-parquet-with-chsql-duckdb</link><guid isPermaLink="true">https://blog.gigapipe.com/merging-parquet-with-chsql-duckdb</guid><category><![CDATA[duckDB]]></category><category><![CDATA[ClickHouse]]></category><category><![CDATA[SQL]]></category><category><![CDATA[opensource]]></category><dc:creator><![CDATA[Lorenzo Mangani]]></dc:creator><pubDate>Sun, 13 Oct 2024 22:00:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1728724569538/352e7b61-b25d-4a41-9108-8b9120a640d9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://community-extensions.duckdb.org/extensions/chsql.html"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728724636889/1747181b-ea86-4097-b4cf-783679b6deaa.png" alt class="image--center mx-auto" /></a></p>
<p>If you’re familiar with our blog, you already know about our <a target="_blank" href="https://duckdb.org">DuckDB</a> Community Extension <a target="_blank" href="https://community-extensions.duckdb.org/extensions/chsql.html">chsql</a> providing a growing number of <strong>ClickHouse SQL</strong> macros and functions for DuckDB users. You can install <a target="_blank" href="https://community-extensions.duckdb.org/extensions/chsql.html"><strong>chsql</strong></a> right from DuckDB SQL:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">INSTALL</span> chsql <span class="hljs-keyword">FROM</span> community;
<span class="hljs-keyword">LOAD</span> chsql;
</code></pre>
<h3 id="heading-merging-parquet-files-with-chsql-mergetree">Merging Parquet files with chsql mergetree</h3>
<p>Today we’re introducing a new original function: <code>read_parquet_mergetree</code></p>
<p>If you're a fan of <strong>ClickHouse</strong>'s MergeTree engine and you're looking to supercharge your data workflow within <strong>DuckDB</strong>, the new <code>read_parquet_mergetree</code> function from our <strong>chsql</strong> extension is going to be your new best friend. It’s a memory-efficient, easy-to-use feature that lets you merge multiple Parquet files with sorting capabilities, emulating the best parts of ClickHouse's powerful data merging strategy.</p>
<h3 id="heading-what-does-readparquetmergetree-do">What Does <code>read_parquet_mergetree</code> do?</h3>
<p>This new function does exactly what it says on the tin: it reads and <strong>merges multiple Parquet files</strong> based on a user-specified <strong>primary sort key</strong>. Think of it as ClickHouse’s <strong>MergeTree</strong> engine for <strong>DuckDB</strong>, but tailored to handle massive datasets <strong>without hogging your system's memory</strong>.</p>
<p>The result? You get <strong>compact, sorted Parquet files</strong> that are ready for <em>lightning-fast range queries.</em></p>
<h3 id="heading-why-should-you-care">Why should You care? 🚀</h3>
<p><em>Here’s the TL;DR:</em></p>
<ul>
<li><p><strong>Efficient Merging:</strong> Combine data from multiple Parquet files just like how ClickHouse <strong>MergeTree</strong> tables consolidate data.</p>
</li>
<li><p><strong>Sort and Compact:</strong> Set a primary sort key to organize your data, optimizing it for fast queries and analysis.</p>
</li>
<li><p><strong>Memory Savvy:</strong> Perfect for large datasets where memory constraints matter.</p>
</li>
<li><p><strong>Wildcard Support:</strong> Supports glob patterns and wildcards as <strong>read_parquet</strong></p>
</li>
</ul>
<h3 id="heading-how-to-use-it">How to Use It</h3>
<p>Here's the beauty of <code>read_parquet_mergetree</code>—it fits seamlessly into your <a target="_blank" href="https://duckdb.org">DuckDB</a> workflow. The syntax is intuitive for ClickHouse users but also simple for anyone new:</p>
<pre><code class="lang-sql">COPY (<span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> read_parquet_mergetree([<span class="hljs-string">'/folder/*.parquet'</span>], <span class="hljs-string">'some_key'</span>)) 
<span class="hljs-keyword">TO</span> <span class="hljs-string">'sorted.parquet'</span>;
</code></pre>
<p>This command:</p>
<ul>
<li><p>Reads all Parquet files in the folder (thanks to wildcard support),</p>
</li>
<li><p>Merges them based on the selected primary sort key (<code>some_key</code>),</p>
</li>
<li><p>Outputs the <strong>sorted</strong> and <strong>compacted</strong> result into a <em>new Parquet file.</em></p>
</li>
</ul>
<h2 id="heading-real-world-benchmark-memory-efficiency">Real-World Benchmark: Memory Efficiency</h2>
<p>To illustrate how much memory <code>read_parquet_mergetree</code> can save you, consider this benchmark:</p>
<pre><code class="lang-sql">COPY (<span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> read_parquet([<span class="hljs-string">'/folder/*.parquet'</span>]) <span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span> some_key) 
<span class="hljs-keyword">TO</span> <span class="hljs-string">'sorted.parquet'</span>;
</code></pre>
<p>The <strong>read_parquet</strong> function uses <strong>all of our system’s RAM (64GB)</strong> to run the task.</p>
<p>Now, let’s compare that to <code>read_parquet_mergetree</code>:</p>
<pre><code class="lang-sql">COPY (<span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> read_parquet_mergetree([<span class="hljs-string">'/folder/*.parquet'</span>], <span class="hljs-string">'some_key'</span>)) 
<span class="hljs-keyword">TO</span> <span class="hljs-string">'sorted.parquet'</span>;
</code></pre>
<p>On the same system, the <strong>read_parquet_mergetree</strong> function query uses only <strong>~800MB of RAM usage</strong> <em>(~80x optimization)</em>, making it perfect for those working with large datasets on resource-constrained systems.</p>
<p><em>Used in combination with</em> <strong><em>HTTPFS</em></strong> <em>it can be used to merge remote files, too!</em></p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>If you're working with <strong>large-scale data</strong> and need a tool that can <strong>merge and sort Parquet files</strong> efficiently, the <code>read_parquet_mergetree</code> function in the <strong>chsql</strong> extension is a game changer. Whether you're a ClickHouse user or a DuckDB enthusiast, this feature allows you to manage your data with unparalleled efficiency.</p>
<p><em>Try it out if you need to merge fast, compact, and sorted Parquet files! 🐤</em></p>
<h3 id="heading-join-our-community">Join our Community</h3>
<p><em>Got ideas for the chsql extension? Join our team, let’s make this happen!</em></p>
<p><a target="_blank" href="https://community-extensions.duckdb.org/extensions/chsql.html"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728724636889/1747181b-ea86-4097-b4cf-783679b6deaa.png" alt class="image--center mx-auto" /></a></p>
]]></content:encoded></item><item><title><![CDATA[$0/month Observability with qryn]]></title><description><![CDATA[Self-Hosted Observability is not only viable - its the best way to get your stack under control without creating dependencies on cloud services or specific service providers and more importantly - without making your job harder in the process.
We run...]]></description><link>https://blog.gigapipe.com/free-observability-with-qryn</link><guid isPermaLink="true">https://blog.gigapipe.com/free-observability-with-qryn</guid><category><![CDATA[always-free]]></category><category><![CDATA[qryn]]></category><category><![CDATA[observability]]></category><category><![CDATA[free]]></category><category><![CDATA[Oracle Cloud]]></category><category><![CDATA[OpenTelemetry]]></category><category><![CDATA[gigapipe]]></category><dc:creator><![CDATA[Alex Maitland]]></dc:creator><pubDate>Wed, 02 Oct 2024 22:00:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727566752184/dff8b1ff-b9ae-4c90-a294-8a4e29d0b6bc.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Self-Hosted Observability</strong> is not only viable - its the best way to get your stack under control without creating dependencies on cloud services or specific service providers and more importantly - without making your job harder in the process.</p>
<p>We run a cloud service and there's no shame in admitting the overhead cost for customers can become a deal breaker when size and scale grow larger than life.</p>
<p><strong>If you’re trying to migrate off Grafana Cloud/AWS/Datadog/etc this is for you!</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727378299764/eb7314d8-8bab-4466-a16f-84613fb2f2ec.png" alt /></p>
<h2 id="heading-self-hosted-all-in-one">⭐ Self-Hosted + All-in-One</h2>
<p>Here's our solid <strong>opensource</strong> recipe you can cook with three simple ingredients:</p>
<ul>
<li><p>⭐ <strong>qryn: all-in-one polyglot stack</strong></p>
<p>  ⭐ <strong>clickhouse: fast OLAP database</strong></p>
<p>  ⭐ <strong>Opentelemetry: industry standard ingestion</strong></p>
</li>
</ul>
<p><em>This will get you covered for any Logs, Metrics, Traces and Profiles - at once.</em></p>
<p><img src="https://scioinfotech.com/wp-content/uploads/2019/10/oracle-cloud-logo-e1572318472568.png" alt="oracle-cloud-logo | Scio Info Tech" /></p>
<h3 id="heading-oracle-cloud-always-free">⭐ Oracle Cloud: Always Free?</h3>
<p><strong>Hello, Oracle.</strong> Now, I'm not their biggest fan but respect where it's due:</p>
<p>The <strong><em>"<mark>Always-Free”</mark></em></strong> Oracle Cloud tier generously allows running <a target="_blank" href="https://www.oracle.com/cloud/compute/arm/">ARM Ampere A1</a> instances with 3,000 CPU hours and 18,000 GB hours per month, which affords exactly 4 CPUs and 24GB of RAM with a ~100GB of storage volume. <em>For free. Forever.</em></p>
<h4 id="heading-grab-a-free-ampere-arm64-instance">Grab a FREE AMPERE ARM64 Instance</h4>
<p>First, sign up to <strong>Oracle Cloud</strong> to gain access to their <strong>Always-Free</strong> Tier.</p>
<ul>
<li><p>Browse to the <strong>Compute</strong> section and <strong>Create a New Instance</strong></p>
</li>
<li><p>Modify the parameters in the <strong>Image and Shape</strong> section by clicking <strong>Edit</strong></p>
</li>
<li><p>Choose an <strong>AMPERE</strong> shape, and <strong><em>max out</em></strong> <em>the resources</em>:</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689622117494/8170ca40-c872-4943-98f2-768848ebdb6b.png" alt class="image--center mx-auto" /></p>
<p><em>A few more clicks and you're ready to go. Refer to the Oracle docs for more options!</em></p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong><mark>Do NOT forget to download the generated keys for SSH access!</mark></strong></div>
</div>

<h3 id="heading-prepare-your-arm64-instance">⭐ Prepare your <strong>ARM64</strong> Instance</h3>
<p>Once your Ampere instance is ready, install the latest <strong>Docker</strong> for <strong>arm64</strong></p>
<pre><code class="lang-bash">$ curl -fsSL https://get.docker.com -o get-docker.sh
$ sudo sh get-docker.sh
</code></pre>
<h3 id="heading-install-qryn">⭐ Install qryn</h3>
<p>The step is magic: use <strong>qryn</strong> transforms your instance into a polyglot stack!</p>
<p><em>Our stack natively supports arm64 architecture, so no special actions are needed.</em></p>
<p>Let’s deploy the full <strong>qryn</strong> bundle using <code>docker compose</code> on our brand new host:</p>
<pre><code class="lang-bash">$ git <span class="hljs-built_in">clone</span> https://github.com/metrico/qryn-oss-demo
$ <span class="hljs-built_in">cd</span> qryn-oss-demo

$ docker compose up -d
</code></pre>
<p><em>Wait for all services to start and you’re ready to go!</em></p>
<p><strong>Grafana</strong> is included on port <strong>3000</strong> preconfigured with all the <strong>qryn</strong> datasources, demo dashboards and demo telemetry instantly ready to explore and play with:</p>
<p><img src="https://user-images.githubusercontent.com/1423657/186014786-165b18da-e808-4cf7-a6fc-eb90df705400.gif" alt /></p>
<h3 id="heading-add-opentelemetry">⭐ Add Opentelemetry</h3>
<p>It’s time to ingest your own data into the system using Opentelemetry.</p>
<p><strong>Opentelemetry</strong> is the standard when it comes to observability instrumentation. Paired with <strong>qryn</strong> it allows methodic ingestion of any telemetry type <em>(Logs, Metrics, Traces, Profiling)</em> using common interfaces compatible with many vendors.</p>
<blockquote>
<p><em>"OTEL" usage = no vendor or tech lock-ins as part of your deployments.</em></p>
</blockquote>
<p>The qryn <a target="_blank" href="https://github.com/metrico/otel-collector"><strong>otel-collector</strong></a> allows ingesting massive amounts of data directly into the bundled <strong>ClickHouse</strong> instance using the native binary drivers and delivering incredible throughput and speed, easily extensible with object storage.</p>
<h3 id="heading-service-ports">⭐ Service Ports</h3>
<p>The qryn API supports ingestion of several protocols but for our high-performance setup we will leverage the <a target="_blank" href="https://github.com/metrico/otel-collector">qryn opentelemetry collector</a> for writing into our database.</p>
<p>The following <strong>service ports</strong> are exposed by the <strong>default qryn collector</strong> config:</p>
<ul>
<li><ul>
<li><p>"3200:3100" <strong># Loki/Logql HTTP receiver</strong></p>
<p>    "3201:3200" <strong># Loki/Logql gRPC receiver</strong></p>
<p>    "8088:8088" <strong># Splunk HEC receiver</strong></p>
<p>    "5514:5514" <strong># Syslog TCP Rereceiverceiver</strong></p>
<p>    "24224:24224" <strong># Fluent Forward receiver</strong></p>
<p>    "4317:4317" <strong># OTLP gRPC receiver</strong></p>
<p>    "4318:4318" <strong># OTLP HTTP receiver</strong></p>
<p>    "14250:14250" <strong># Jaeger gRPC receiver</strong></p>
<p>    "14268:14268" <strong># Jaeger thrift HTTP receiver</strong></p>
<p>    "9411:9411" <strong># Zipkin Trace receiver</strong></p>
<p>    "11800:11800" <strong># Skywalking gRPC receiver</strong></p>
<p>    "12800:12800" <strong># Skywalking HTTP receiver</strong></p>
<p>    "8086:8086" <strong># InfluxDB Line proto HTTP</strong></p>
<p>    "8062:8062" <strong># Pyroscope jprof Receiver</strong></p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-telemetry-agents">⭐ Telemetry Agents</h3>
<p>You’re ready to choose any of the supported agents and start sending data:</p>
<ul>
<li><p><a target="_blank" href="https://vector.dev/"><strong>Vector</strong></a> <em>(logs, metrics)</em></p>
</li>
<li><p><a target="_blank" href="https://grafana.com/docs/agent/latest/"><strong>Grafana Agent</strong></a> <em>(logs, metrics, traces)</em></p>
</li>
<li><p><a target="_blank" href="https://github.com/metrico/otel-collector"><strong>Alloy</strong></a> <em>(logs, metrics, traces, profiling)</em></p>
</li>
<li><p><a target="_blank" href="https://github.com/metrico/otel-collector"><strong>Opentelemetry Collector</strong></a> <em>(logs, metrics, traces, profiling)</em></p>
</li>
<li><p><strong>Any Agent</strong> compatible with the qryn APIs <em>(Loki, Prometheus, Tempo)</em></p>
</li>
</ul>
<p><strong><mark>That’s it! Your free Polyglot Observability stack is ready to use and abuse!</mark></strong></p>
<h3 id="heading-extending-capacity">⭐ Extending Capacity</h3>
<p>If the ~100GB of onboard storage run out, attach <a target="_blank" href="https://blog.qryn.dev">S3 storage to your setup</a> or take a look at our sponsors at <a target="_blank" href="https://gigapipe.com">gigapipe</a> and their flat-price qryn observability SaaS</p>
<p><a target="_blank" href="https://qryn.metrico.in"><img src="https://github.com/metrico/qryn-docs/assets/1423657/a5164f98-d3ed-4638-afe5-c87d252c74af" alt /></a></p>
<blockquote>
<p><em>That's it. One API. One datastore. A thousand formats and use cases</em> 🎉</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Tigris S3 Storage + qryn]]></title><description><![CDATA[Meet Tigris
Tigris is a new distributed S3 compatible object storage operated by Fly.io and offering global bucket replication with low pricing and a generous free tier:

5GB of data storage per month

10,000 PUT, COPY, POST, LIST requests per month
...]]></description><link>https://blog.gigapipe.com/tigris-s3-storage-qryn</link><guid isPermaLink="true">https://blog.gigapipe.com/tigris-s3-storage-qryn</guid><category><![CDATA[tigris]]></category><category><![CDATA[observability]]></category><category><![CDATA[ClickHouse]]></category><category><![CDATA[S3]]></category><category><![CDATA[object storage]]></category><category><![CDATA[qryn]]></category><category><![CDATA[gigapipe]]></category><dc:creator><![CDATA[Alex Maitland]]></dc:creator><pubDate>Mon, 30 Sep 2024 22:00:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727562664483/451302ee-f1b5-4445-859c-815ec5223c80.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://www.tigrisdata.com/"><img src="https://www.tigrisdata.com/docs/logo/light.png" alt class="image--center mx-auto" /></a></p>
<h1 id="heading-meet-tigris">Meet Tigris</h1>
<p><a target="_blank" href="https://www.tigrisdata.com/docs/overview/">Tigris</a> is a new distributed S3 compatible object storage operated by <a target="_blank" href="https://fly.io">Fly.io</a> and offering global bucket replication with low pricing and a generous free tier:</p>
<ul>
<li><p>5GB of data stora<a target="_blank" href="https://www.tigrisdata.com/docs/pricing/#free-allowances">g</a>e per month</p>
</li>
<li><p>10,000 PUT, COPY, POST, LIST requests per month</p>
</li>
<li><p>100,000 GET, SELECT and all other requests per month</p>
</li>
</ul>
<h3 id="heading-example">Example</h3>
<p>Let's say you have a bucket with <strong>100GB</strong> of data and you make <strong>1,000,000 GET</strong> requests to the objects in the bucket. You would be charged as follows:</p>
<ul>
<li><p>Data Storage: 5GB x $0 + 95GB x $0.02/GB/month = <strong>$1.90</strong></p>
</li>
<li><p>PUT Requests: 10,000 x $0 + 90,000 x $0.005/1000 requests = <strong>$0.45</strong></p>
</li>
<li><p>GET Requests: 100,000 x $0 + 900,000 x $0.0005/1000 requests = <strong>$0.45</strong></p>
</li>
<li><p>Data Transfer: <strong>$0</strong></p>
</li>
</ul>
<p><strong>There’s more!</strong> Storage costs are calculated using <strong>GB/month</strong>, determined by averaging the daily peak storage over a monthly period. For example:</p>
<ul>
<li><p>Storing 1 GB constantly for a whole month = 1 GB/month</p>
</li>
<li><p>Storing 10 GB for 12 days + 20 GB for 18 days = 16 GB/month</p>
</li>
</ul>
<p>🚀 Sounds interesting? Get ready! This example shows how to use Tigris buckets as cold storage disk with the ClickHouse S3 Table engine and qryn. Let’s do this.</p>
<h2 id="heading-setup-instructions">Setup Instructions</h2>
<h4 id="heading-get-tigris">Get Tigris</h4>
<ul>
<li>Sign in to your <strong>Fly.io/Tigris account</strong> and create an new bucket, ie:</li>
</ul>
<pre><code class="lang-bash">https://yourbucket.fly.storage.tigris.dev
</code></pre>
<ul>
<li>Generate a <strong>token pair</strong> with write permissions to the bucket, ie:</li>
</ul>
<pre><code class="lang-bash">Access Key ID = XXXXXXXXXXXXXXXXXXXXXXXX
Secret Access Key = YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
</code></pre>
<h4 id="heading-clickhouse">ClickHouse</h4>
<p>Before we proceed, let’s validate our bucket and practice some simple queries.</p>
<ul>
<li><p>Configure an <strong>S3 table</strong> in ClickHouse using <strong>Parquet</strong> format</p>
</li>
<li><p>Configure the <strong>S3 Engine</strong> with your <strong>Tigris</strong> bucket and tokens</p>
</li>
<li><p>Configure <strong>max_threads, max_insert_threads</strong> based on your CPU cores</p>
</li>
</ul>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> s3_tigris (<span class="hljs-keyword">name</span> <span class="hljs-keyword">String</span>, <span class="hljs-keyword">value</span> UInt32) 
   <span class="hljs-keyword">ENGINE</span>=S3(<span class="hljs-string">'https://yourbucket.fly.storage.tigris.dev/somefolder/sometable.csv'</span>, <span class="hljs-string">'XXXXXXXXXXXXXXXXXXXXXXXX'</span>, <span class="hljs-string">'YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'</span>, <span class="hljs-string">'Parquet'</span>) 
   <span class="hljs-keyword">SETTINGS</span> max_threads=<span class="hljs-number">8</span>, max_insert_threads=<span class="hljs-number">8</span>, input_format_parallel_parsing=<span class="hljs-number">0</span>, input_format_with_names_use_header=<span class="hljs-number">0</span>;
</code></pre>
<ul>
<li><code>INSERT</code> &amp; <code>SELECT</code> data using the Tigris storage table</li>
</ul>
<pre><code class="lang-sql"><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> s3_tigris <span class="hljs-keyword">VALUES</span> (<span class="hljs-string">'one'</span>, <span class="hljs-number">1</span>), (<span class="hljs-string">'two'</span>, <span class="hljs-number">2</span>), (<span class="hljs-string">'three'</span>, <span class="hljs-number">3</span>);
</code></pre>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> s3_tigris <span class="hljs-keyword">LIMIT</span> <span class="hljs-number">2</span>;
</code></pre>
<p><em>Alrigh! If everything works as expected, we’re ready to steam right ahead.</em></p>
<h3 id="heading-tigris-storage-for-qryn">Tigris Storage for qryn</h3>
<p>Manual queries are fun - next let's configure Tigris as a ClickHouse <em>storage disk for our qryn instance to store our Logs, Metrics, Traces and Profiling data.</em></p>
<p>Here’s an overly simple configuration using S3 as the only storage for our data.</p>
<ul>
<li><p>Configure an <a target="_blank" href="https://clickhouse.com/docs/en/operations/storing-data"><strong>S3 disk</strong></a> with <strong>data_cache_enabled</strong></p>
</li>
<li><p>Configure a <a target="_blank" href="https://clickhouse.com/docs/en/operations/system-tables/storage_policies"><strong>storage policy</strong></a> to automatically manage our cold storage</p>
</li>
<li><p>Configure <a target="_blank" href="https://clickhouse.com/docs/en/operations/storing-data"><strong>data_cache_max_size</strong></a> based on your storage configuration</p>
</li>
<li><p>Configure <a target="_blank" href="https://clickhouse.com/docs/en/operations/system-tables/storage_policies"><strong>move_factor</strong></a> based on the <a target="_blank" href="https://clickhouse.com/docs/en/operations/system-tables/storage_policies">desired ratio</a></p>
</li>
</ul>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">yandex</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">storage_configuration</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">disks</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">tigris</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">type</span>&gt;</span>s3<span class="hljs-tag">&lt;/<span class="hljs-name">type</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">endpoint</span>&gt;</span>https://yourbucket.fly.storage.tigris.dev/fakekey<span class="hljs-tag">&lt;/<span class="hljs-name">endpoint</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">access_key_id</span>&gt;</span>XXXXXXXXXXXXXXXXXXXXXXXX<span class="hljs-tag">&lt;/<span class="hljs-name">access_key_id</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">secret_access_key</span>&gt;</span>YYYYYYYYYYYYYYYYYYYY<span class="hljs-tag">&lt;/<span class="hljs-name">secret_access_key</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">data_cache_enabled</span>&gt;</span>1<span class="hljs-tag">&lt;/<span class="hljs-name">data_cache_enabled</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">data_cache_max_size</span>&gt;</span>8589934592<span class="hljs-tag">&lt;/<span class="hljs-name">data_cache_max_size</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">tigris</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">disks</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">policies</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">external</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">volumes</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">s3</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">disk</span>&gt;</span>tigris<span class="hljs-tag">&lt;/<span class="hljs-name">disk</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">s3</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">volumes</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">external</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">tiered</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">move_factor</span>&gt;</span>0.05<span class="hljs-tag">&lt;/<span class="hljs-name">move_factor</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">volumes</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">hot</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">disk</span>&gt;</span>ssd<span class="hljs-tag">&lt;/<span class="hljs-name">disk</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">hot</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">s3</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">disk</span>&gt;</span>tigris<span class="hljs-tag">&lt;/<span class="hljs-name">disk</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">prefer_not_to_merge</span>&gt;</span>true<span class="hljs-tag">&lt;/<span class="hljs-name">prefer_not_to_merge</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">s3</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">volumes</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">tiered</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">policies</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">storage_configuration</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">yandex</span>&gt;</span>
</code></pre>
<p><em>Note: Performance may vary based on network conditions and available resources</em></p>
<p>🗨️ <mark>If you have feedback or use </mark> <strong><mark>Tigris Buckets</mark></strong> <mark> with </mark> <strong><mark>ClickHouse </mark></strong> <mark>and </mark> <strong><mark>qryn</mark></strong><mark>, please consider </mark> <a target="_blank" href="https://github.com/metrico/qryn/issues"><mark>sharing your test results with our community</mark></a><mark>!</mark></p>
<hr />
<h4 id="heading-reference-links">Reference Links</h4>
<p><em>Interested in this subject?</em> Check out the following links for further information</p>
<ul>
<li><p>https://clickhouse.com/docs/en/engines/table-engines/integrations/s3/</p>
</li>
<li><p>https://altinity.com/blog/tips-for-high-performance-clickhouse-clusters-with-s3-object-storage</p>
</li>
<li><p><a target="_blank" href="https://blog.qryn.dev/cloudflare-r2-clickhouse">https://blog.qryn.dev/cloudflare-r2-clickhouse</a></p>
</li>
</ul>
<p><a target="_blank" href="https://qryn.dev"><img src="https://github.com/metrico/qryn-docs/assets/1423657/a5164f98-d3ed-4638-afe5-c87d252c74af" alt class="image--center mx-auto" /></a></p>
]]></content:encoded></item><item><title><![CDATA[🔎 Cloudflare Tail Workers + qryn]]></title><description><![CDATA[Supercharge Your Observability: Using Cloudflare Tail Workers with qryn
In the ever-evolving landscape of cloud computing and web services, observability has become a critical aspect of maintaining robust and efficient systems. Today, we're excited t...]]></description><link>https://blog.gigapipe.com/cloudflare-tail-workers-qryn</link><guid isPermaLink="true">https://blog.gigapipe.com/cloudflare-tail-workers-qryn</guid><category><![CDATA[cloudflare]]></category><category><![CDATA[workers]]></category><category><![CDATA[Logs]]></category><category><![CDATA[observability]]></category><category><![CDATA[qryn]]></category><category><![CDATA[gigapipe]]></category><category><![CDATA[loki]]></category><category><![CDATA[Grafana]]></category><dc:creator><![CDATA[Jachen Duschletta]]></dc:creator><pubDate>Thu, 26 Sep 2024 18:46:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727375092861/200aa8c7-c726-4404-ad1c-45e7467422a4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://qryn.dev"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727375161950/71095e9a-fa77-4ea2-8773-b5f78444659f.png" alt class="image--center mx-auto" /></a></p>
<h2 id="heading-supercharge-your-observability-using-cloudflare-tail-workers-with-qryn">Supercharge Your Observability: Using Cloudflare Tail Workers with qryn</h2>
<p>In the ever-evolving landscape of cloud computing and web services, observability has become a critical aspect of maintaining robust and efficient systems. Today, we're excited to explore a powerful combination: <a target="_blank" href="https://developers.cloudflare.com/workers/observability/logs/tail-workers/">Cloudflare Tail Workers</a> and <a target="_blank" href="https://qryn.dev">qryn, the polyglot observability stack compatible with Loki, Prometheus, Tempo and Pyroscope</a>. This integration allows you to stream logs and events from your Cloudflare Workers directly into your observability platform, providing real-time insights and enhancing your ability to monitor and troubleshoot your applications.</p>
<h3 id="heading-what-are-cloudflare-tail-workers">What are Cloudflare Tail Workers?</h3>
<p><a target="_blank" href="https://developers.cloudflare.com/workers/observability/logs/tail-workers/">Cloudflare Tail Workers</a> are a special type of Cloudflare Worker that allows you to process and forward logs and events from your other Workers in real-time. They act as a "tail" to your main Workers, catching and processing the output stream. This feature is incredibly useful for:</p>
<ol>
<li><p>Real-time log analysis</p>
</li>
<li><p>Error tracking and alerting</p>
</li>
<li><p>Performance monitoring</p>
</li>
<li><p>Security event processing</p>
</li>
</ol>
<p><a target="_blank" href="https://developers.cloudflare.com/workers/observability/logs/tail-workers/">Tail Workers</a> receive batches of events from your main Workers, allowing you to process, filter, or forward these events to external systems – in our case, <a target="_blank" href="https://qryn.dev">qryn</a>.</p>
<p><img src="https://developers.cloudflare.com/_astro/tail-workers.CaYo-ajt_19unHR.webp" alt="Tail Worker diagram" /></p>
<h2 id="heading-configuring-cloudflare-tail-workers">Configuring Cloudflare Tail Workers</h2>
<p>To set up a Tail Worker, follow these steps:</p>
<ol>
<li><p>Log in to your <strong>Cloudflare</strong> dashboard.</p>
</li>
<li><p>Navigate to the <strong>Workers</strong> section.</p>
</li>
<li><p>Click <strong>"Create a Service"</strong> and choose <strong>"Tail Worker"</strong> as the type.</p>
</li>
<li><p>Give your <strong>Tail Worker</strong> a name and click <strong>"Create Service"</strong>.</p>
</li>
<li><p>In the editor, paste the code for qryn ingestion (shown below).</p>
</li>
<li><p><strong>Save</strong> and <strong>Deploy</strong> your Tail Worker.</p>
</li>
</ol>
<h2 id="heading-connecting-tail-workers-to-main-workers">Connecting Tail Workers to Main Workers</h2>
<p>Add the following to the <code>wrangler.toml</code> file of your Main Worker(s):</p>
<pre><code class="lang-bash">tail_consumers = [{service = <span class="hljs-string">"&lt;TAIL_WORKER_NAME&gt;"</span>}]
</code></pre>
<p>Alternatively use the following procedure through the Cloudflare User-Interface:</p>
<ol>
<li><p>Go to the main <strong>Worker</strong> you want to monitor.</p>
</li>
<li><p>In the <strong>Settings</strong> tab, find the <strong>"Tail Workers"</strong> section.</p>
</li>
<li><p>Select your newly created <strong>Tail Worker</strong> from the dropdown.</p>
</li>
<li><p><strong>Save</strong> the changes.</p>
</li>
</ol>
<p><em>Now, your Tail Worker will receive events from the main Worker.</em></p>
<h3 id="heading-the-code-tail-worker-for-qryn">The Code: Tail Worker for qryn</h3>
<p>Here's our Tail Worker code that processes events and sends them to qryn.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Function to create Loki POST query</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createLokiPostQuery</span>(<span class="hljs-params">data</span>) </span>{
  <span class="hljs-keyword">const</span> streams = data.map(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> {
    <span class="hljs-keyword">const</span> labels = {
      <span class="hljs-attr">scriptName</span>: item.scriptName,
      <span class="hljs-attr">outcome</span>: item.outcome,
      <span class="hljs-attr">url</span>: item.event.request.url,
      <span class="hljs-attr">method</span>: item.event.request.method,
      <span class="hljs-attr">colo</span>: item.event.request.cf.colo
    };

    <span class="hljs-keyword">const</span> labelString = <span class="hljs-built_in">Object</span>.entries(labels)
      .map(<span class="hljs-function">(<span class="hljs-params">[key, value]</span>) =&gt;</span> <span class="hljs-string">`<span class="hljs-subst">${key}</span>="<span class="hljs-subst">${value}</span>"`</span>)
      .join(<span class="hljs-string">','</span>);

    <span class="hljs-keyword">const</span> entries = [];

    <span class="hljs-comment">// Process logs</span>
    item.logs.forEach(<span class="hljs-function"><span class="hljs-params">log</span> =&gt;</span> {
      entries.push({
        <span class="hljs-attr">ts</span>: log.timestamp.toString() + <span class="hljs-string">'000000'</span>, <span class="hljs-comment">// Convert to nanoseconds</span>
        <span class="hljs-attr">line</span>: <span class="hljs-built_in">JSON</span>.stringify({
          <span class="hljs-attr">level</span>: log.level,
          <span class="hljs-attr">message</span>: log.message.join(<span class="hljs-string">' '</span>)
        })
      });
    });

    <span class="hljs-comment">// Process exceptions</span>
    item.exceptions.forEach(<span class="hljs-function"><span class="hljs-params">exception</span> =&gt;</span> {
      entries.push({
        <span class="hljs-attr">ts</span>: exception.timestamp.toString() + <span class="hljs-string">'000000'</span>, <span class="hljs-comment">// Convert to nanoseconds</span>
        <span class="hljs-attr">line</span>: <span class="hljs-built_in">JSON</span>.stringify({
          <span class="hljs-attr">type</span>: <span class="hljs-string">'exception'</span>,
          <span class="hljs-attr">name</span>: exception.name,
          <span class="hljs-attr">message</span>: exception.message
        })
      });
    });

    <span class="hljs-comment">// Process diagnosticsChannelEvents</span>
    item.diagnosticsChannelEvents.forEach(<span class="hljs-function"><span class="hljs-params">event</span> =&gt;</span> {
      entries.push({
        <span class="hljs-attr">ts</span>: event.timestamp.toString() + <span class="hljs-string">'000000'</span>, <span class="hljs-comment">// Convert to nanoseconds</span>
        <span class="hljs-attr">line</span>: <span class="hljs-built_in">JSON</span>.stringify({
          <span class="hljs-attr">type</span>: <span class="hljs-string">'diagnosticsChannel'</span>,
          <span class="hljs-attr">channel</span>: event.channel,
          <span class="hljs-attr">message</span>: event.message
        })
      });
    });

    <span class="hljs-keyword">return</span> {
      <span class="hljs-attr">stream</span>: {
        [labelString]: <span class="hljs-string">''</span>
      },
      <span class="hljs-attr">values</span>: entries.map(<span class="hljs-function"><span class="hljs-params">entry</span> =&gt;</span> [entry.ts, entry.line])
    };
  });

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">streams</span>: streams
  };
}

<span class="hljs-comment">// Cloudflare Worker</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  <span class="hljs-keyword">async</span> tail(events) {
    <span class="hljs-comment">// Process events using our createLokiPostQuery function</span>
    <span class="hljs-keyword">const</span> lokiPostQuery = createLokiPostQuery(events);

    <span class="hljs-comment">// Grafana Loki API endpoint</span>
    <span class="hljs-keyword">const</span> lokiApiUrl = <span class="hljs-string">'https://qryn.server/loki/api/v1/push'</span>;

    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(lokiApiUrl, {
        <span class="hljs-attr">method</span>: <span class="hljs-string">'POST'</span>,
        <span class="hljs-attr">headers</span>: {
          <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>,
        },
        <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify(lokiPostQuery),
      });

      <span class="hljs-keyword">if</span> (!response.ok) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`HTTP error! status: <span class="hljs-subst">${response.status}</span>`</span>);
      }

      <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> response.text();
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Successfully sent data to Loki:'</span>, result);
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error sending data to Loki:'</span>, error);
    }
  }
};
</code></pre>
<p>☝️ <mark>Update the </mark> <strong><mark>lokiApiUrl </mark></strong> <mark>parameter with your </mark> <a target="_blank" href="https://qryn.dev"><mark>qryn </mark></a> <mark>or </mark> <a target="_blank" href="https://qryn.cloud"><mark>qryn.cloud</mark></a> <mark> URL endpoint </mark> ☝️</p>
<h2 id="heading-ready-to-tail">🔎 Ready to Tail</h2>
<p>Your Workers tail should appear in your qryn instance. Select by using CloudFlare Workers Tail labels and search/filter/transform the logs using the <strong>Logs Explorer</strong>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1678209595876/26a84aca-e64a-4dda-9214-8da32adbb908.png?auto=compress,format&amp;format=webp" alt /></p>
<h3 id="heading-tail-worker-labels">🔎 Tail Worker Labels:</h3>
<ul>
<li><em>scriptName, outcome, url, method, colo</em></li>
</ul>
<h3 id="heading-benefits-of-this-integration">🎱 Benefits of This Integration</h3>
<p>By integrating Cloudflare Tail Workers with qryn, you instantly gain:</p>
<ol>
<li><p><strong>Real-time log streaming</strong>: Get instant visibility into your Workers' behavior.</p>
</li>
<li><p><strong>Centralized observability</strong>: Collect logs from all your Workers in one place.</p>
</li>
<li><p><strong>Advanced querying and visualization</strong>: Leverage qryn's powerful features and its native integration with Grafana to analyze your data.</p>
</li>
<li><p><strong>Scalability</strong>: Handle high volumes of log data with ease.</p>
</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The combination of <strong>Cloudflare Tail Workers</strong> and <strong>qryn</strong> offers a robust solution for real-time observability of your Cloudflare Workers. By following the steps outlined in this post, you can set up a powerful logging pipeline that will give you deeper insights into your applications' performance and behavior.</p>
<p>Remember, good observability practices are key to maintaining reliable and efficient systems. Start leveraging these tools today, and take your monitoring capabilities to the next level!</p>
<p><a target="_blank" href="https://qryn.dev"><img src="https://github.com/metrico/qryn-docs/assets/1423657/a5164f98-d3ed-4638-afe5-c87d252c74af" alt class="image--center mx-auto" /></a></p>
]]></content:encoded></item><item><title><![CDATA[💠 Altinity Cloud Observability with qryn]]></title><description><![CDATA[Earlier this week our friends at Altinity released a guide on configuring Altinity Cloud stack observability using Grafana Cloud. Since qryn is a drop-in Grafana Cloud replacement (and so much more) with native ClickHouse storage, this is our redempt...]]></description><link>https://blog.gigapipe.com/altinity-cloud-observability-with-qryn</link><guid isPermaLink="true">https://blog.gigapipe.com/altinity-cloud-observability-with-qryn</guid><category><![CDATA[observability]]></category><category><![CDATA[qryn]]></category><category><![CDATA[Grafana]]></category><category><![CDATA[ClickHouse]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[monitoring]]></category><category><![CDATA[opensource]]></category><category><![CDATA[gigapipe]]></category><dc:creator><![CDATA[Jachen Duschletta]]></dc:creator><pubDate>Wed, 25 Sep 2024 11:05:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727125039614/b12cbd5e-5595-44db-b12a-fcdcada06e8e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727167481993/e21c28eb-ec6d-4b3a-af2b-5a932958c73d.png" alt /></p>
<p><em>Earlier this week our friends at</em> <a target="_blank" href="https://altinity.com"><em>Altinity</em></a> <em>released a</em> <a target="_blank" href="https://altinity.com/blog/how-to-monitor-metrics-and-logs-from-altinity-cloud-in-grafana-cloud"><em>guide</em></a> <em>on configuring Altinity Cloud stack observability using Grafana Cloud. Since qryn is a drop-in Grafana Cloud replacement (and so much more) with native ClickHouse storage, this is our redemption guide for Altinity Ops</em> 😉</p>
<h3 id="heading-altinity-cloud-observability-with-qryn">▶️ Altinity Cloud Observability with qryn</h3>
<p>In this blog post, we’ll show you how to keep an eye on your <strong>Altinity-managed ClickHouse</strong> clusters using the <strong>qryn</strong> polyglot observability stack (or <strong>Gigapipe Cloud)</strong></p>
<p>With the <a target="_blank" href="https://altinity.com"><em>Altinity</em></a> Cloud Manager, you can quickly configure your ClickHouse environment to send observability signals into qryn using Prometheus and Loki APIs and instantly use Grafana to create and share real-time visualizations and alerts.</p>
<p>We’ll walk you through the steps to:</p>
<ul>
<li><p>Setup <a target="_blank" href="https://qryn.dev"><strong>qryn</strong></a> or sign-up for <a target="_blank" href="https://qryn.cloud">Gigapipe Cloud</a></p>
</li>
<li><p>Send <strong>Prometheus</strong> metrics to <strong>qryn</strong></p>
</li>
<li><p>Send <strong>ClickHouse Logs</strong> to <strong>qryn</strong></p>
</li>
<li><p>Visualize and Explore data with <strong>Grafana</strong> and <strong>qryn</strong></p>
</li>
</ul>
<blockquote>
<p><mark>GOAL: Store qryn data in Altinity Cloud to kill Observability costs!</mark></p>
</blockquote>
<p><a target="_blank" href="https://qryn.dev"><img src="https://github.com/metrico/qryn-docs/assets/1423657/a5164f98-d3ed-4638-afe5-c87d252c74af" alt /></a></p>
<h3 id="heading-get-polyglot">▶️ Get Polyglot</h3>
<p>If you already have a <a target="_blank" href="https://qryn.metrico.in/#/installation"><strong>qryn</strong> setup</a>, <em>you’re ready to go!</em></p>
<p>⚙️ If you’re using K8s: <a target="_blank" href="https://github.com/metrico/qryn-helm">https://github.com/metrico/qryn-helm</a></p>
<p>⚙️ If you’d like a quick local setup use our <a target="_blank" href="https://github.com/metrico/qryn-oss-demo">qryn-demo</a> bundle.</p>
<p>⚙️ If you’d like to keep things cloudy, <a target="_blank" href="https://gigapipe.com/">signup for an Account on Gigapipe</a></p>
<h3 id="heading-setup-prometheus-qryn">▶️ Setup Prometheus + qryn</h3>
<p>The <strong>qryn</strong> stack supports the <strong>Prometheus API</strong> out of the box, including <strong>remote_write</strong> capabilities. Using qryn and/or Gigapipe cloud, all you need is the <strong>qryn service URL</strong> and <em>optional Authentication and Partitioning tokens</em>:</p>
<ul>
<li><p>From your <strong>Altinity Cloud Manager</strong> browse your list of <strong>environments</strong>, selecting the one you want to monitor and use the three-dot menu to <strong>edit the settings</strong>.</p>
</li>
<li><p>In the <strong>Environment Configuration</strong> dialog select the <strong>Metrics</strong> tab to configure</p>
</li>
<li><p>Once there, find the <strong>External Prometheus</strong> section and enter the <strong>qryn ingestions URL</strong> into the <strong>Remote URL</strong> field. <em>When using Gigapipe, remember to add the API-Key as the</em> <strong><em>Auth User</em></strong> <em>and finally add the API-Secret as the</em> <strong><em>Auth Password</em></strong>*.*</p>
<pre><code class="lang-plaintext">  https://qryn.local:3100/api/v1/prom/remote/write
  -- OR -- 
  https://qryn.gigapipe.com/api/v1/prom/remote/write
</code></pre>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727118943043/70be8ef8-e6cb-47bc-9422-65ce2d8009fd.png" alt class="image--center mx-auto" /></p>
<p>  Click the <strong>OK</strong> button to save the changes. This will <strong>activate</strong> the required connections and initiate metrics generation towards your qryn instance</p>
</li>
</ul>
<h3 id="heading-setup-loki-logs-qryn">▶️ Setup Loki Logs + qryn</h3>
<p>Let’s perform the same operation to emit our Logs using the <strong>qryn Loki API</strong></p>
<ul>
<li><p>From your <strong>Altinity Cloud Manager</strong> browse your list of <strong>environments</strong>, selecting the one you want to monitor and use the three-dot menu to <strong>edit the settings</strong>.</p>
</li>
<li><p>In the <strong>Environment Configuration</strong> dialog select the <strong>Metrics</strong> tab to configure</p>
</li>
<li><p>Once there, find the <strong>External Loki URL</strong> field, where you provide the qryn API URL</p>
<pre><code class="lang-plaintext">  https://qryn.local:3100/loki/api/v1/push
  -- OR --
  https://API-KEY:API-SECRET@qryn.gigapipe.com/loki/api/v1/push
</code></pre>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727118969291/b944e3f5-d366-4980-b314-9cfa9b296716.png" alt class="image--center mx-auto" /></p>
<p>  Click the <strong>OK</strong> button to save the changes, which will <strong>activate</strong> the connection and send logs to your qryn or Gigapipe Cloud instance. <em>We’re all set!</em></p>
</li>
</ul>
<h3 id="heading-exploring-metrics">🔎 Exploring Metrics</h3>
<p>Let’s open our qryn <strong>Grafana</strong> or <strong>Gigapipe Grafana</strong> to browse our datasources.</p>
<p>Navigate to the <strong>Explore</strong> tab and use the <strong>New Metric Exploration</strong> application to see an overview of all the received <strong>Altinity Cloud Metrics</strong>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727119036074/dbd46faf-c573-421b-8417-fafce4076252.png" alt class="image--center mx-auto" /></p>
<p>The <strong>labels</strong> will help you select the correct parts of your cluster and to generate queries to observe our stack from multiple angles. Each of these label based queries can be added into a dashboard or used for alerting, right from inside Grafana using the <strong>Select</strong> button to start. Each visualization is labeled with the name of the metric itself</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXeFE2ZojDiQaMvqXXzS_2BdVhwyHXjsBd_jSDoOPBPzXBHRNiXJyOEvzLhC7B6keclW_f8GY__C47ELrhprGxiRch5aFGFU0cTXJXydZHVnimRnb5Hzv6wiIHAA-X9D73ryLlFvKRZSIRh8Je2WTETNiHA?key=6FY221Nv0HBmhbq4R2Ye6Q" alt /></p>
<p>Every metric sent to the Prometheus server has one or more labels attached to it. We can filter what we see by selecting one or more labels. Click the <strong>Add label</strong> button to add a label to the query. When you click the button, you’ll see a dropdown list of labels from all the metrics sent to this server:</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXdMN1l0tRQ2muu9APv21r6dFEtDFeGjIPIXLABWH1j5dxJSZaiJM4p8RUol-I8SgfqPeBceyGDidYrU9JN5RLqrJUJJo0PJBaguHGcbXWs129KDDfPQ4ml2_gIsKXMenUD0WaO8XyhTeyb9OFYkLNG7_G_t?key=6FY221Nv0HBmhbq4R2Ye6Q" alt /></p>
<h3 id="heading-exploring-logs">🔎 Exploring Logs</h3>
<p>Click the <strong>Label browser</strong> button to see the labels available in your qryn instance:</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXfIcvP2xFoP6HrkeTjjRlNEabn_iNedIRNN01hZ4PilxMXD3p_1MS3-xM_pNLH4B8UNqbxHqGGlXyg3JfcYCLCjTW0w96k75vy-f3bt7l_9vsHqOkRRiHXbaQgw-NHtMUCC9W9bgvvW0SkPTi7og0zwWPo?key=6FY221Nv0HBmhbq4R2Ye6Q" alt /></p>
<p>Let’s start with some well known labels: <code>namespace</code> and <code>pod</code>.</p>
<p>Browse and select the available values to see logs produced by them:</p>
<p><code>{namespace="altinity-maddie-na",pod="clickhouse-operator-7778d7cfb6-9q8ml"}</code></p>
<p>Click the <strong>Show Logs</strong> button to see all the matching messages from those services.</p>
<p><em><mark>That’s it! You’re ready to use LogQL features to filter, extract and transform logs!</mark></em></p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXe-P-tzadEcpenr0cagTg7SBXg26fAzZMiyDPJ627OqGcWsyT4AoexLsc8kn6hi3G4p2eQ6HU_sCWi-Hn-Q6puaA_BPF17TLT3Jx6pmRYEEey8CWQDaUiz48zrxKsmj6beQog9gV7H04rja568W6Iid_Yd8?key=6FY221Nv0HBmhbq4R2Ye6Q" alt /></p>
<h3 id="heading-advanced-techniques">🎱 Advanced Techniques</h3>
<p>Logs can be used to create ad hoc statistics about rates of errors or count of ‘query types’ over time, to allow alerting or monitoring of important health related status logs.</p>
<p>To make ad hoc statistics, we can use a query that filters the logs to something interesting (e.g. Logs that contain the word ‘Error’) and then use a counter query to see the amount of occurrences of the Error logs over time.</p>
<pre><code class="lang-plaintext">count_over_time({job="dummy-server"} |~ `Error` [$__auto])
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727211745563/1074bf42-e0c8-4bd5-95e4-32aee411a466.png" alt class="image--center mx-auto" /></p>
<p>This allows us to see how many Errors are occurring and if they increase over time. Using this query, we can also set an alert to detect if Errors pass a threshold.</p>
<p>To add an alert, we can navigate to the <strong>Alerting</strong> Menu inside Grafana and select <strong>Manage Alert Rules</strong>. Once there, we click <strong>New Alert rule</strong> to create a new rule.</p>
<p>Now we can use the above query to create an Alert for ‘Error’ log lines. Using this alert we can now get notified by Grafana when our cumulative ‘Error’ count goes above 100 in a 10 minutes span.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727212164510/7da47011-54ff-4ccf-918a-0b2ea871e846.png" alt class="image--center mx-auto" /></p>
<p>Through the power of qryn and Grafana, you can be alerted on Metric spikes, Log rates and Occurrences. Making it faster to find and resolve issues in your system.</p>
<h3 id="heading-conclusion">👁️‍🗨️ Conclusion</h3>
<p><em>If you use Clickhouse, use it all the way and store your observability in OLAP!</em></p>
<p>With <a target="_blank" href="https://qryn.dev">qryn</a> you get the same APIs and features as Grafana Cloud as a thin overlay on top of your existing ClickHouse storage, retaining control of costs, storage and without dependencies on third-party providers and their usage based plans…</p>
<blockquote>
<p>Don’t drop your data to save. Drop your expensive Observability provider!</p>
</blockquote>
<p><a target="_blank" href="https://gigapipe.com"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727275637837/d1d81bd3-f337-46ec-b23a-8e5ace8178f0.png" alt class="image--center mx-auto" /></a></p>
]]></content:encoded></item><item><title><![CDATA[📈 Profiling² with Qryn Adventures pt. 1]]></title><description><![CDATA[Profiling performance will take you places - and at times - the improvement process can spin off into quite interesting journeys often leading to unexpected results. Performance tuning is one of significant parts for qryn development, and we are extr...]]></description><link>https://blog.gigapipe.com/profiling-with-qryn</link><guid isPermaLink="true">https://blog.gigapipe.com/profiling-with-qryn</guid><category><![CDATA[qryn]]></category><category><![CDATA[pyroscope]]></category><category><![CDATA[profiling]]></category><category><![CDATA[continuous profiling]]></category><category><![CDATA[pprof]]></category><category><![CDATA[PGO]]></category><category><![CDATA[golang]]></category><dc:creator><![CDATA[Volodymyr Akchurin]]></dc:creator><pubDate>Wed, 04 Sep 2024 09:15:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727377845373/abf39a98-61e4-4961-9c36-5086e894a096.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://qryn.dev"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1725387322397/4048aecc-61f2-482a-938b-9155c99230d5.png" alt class="image--center mx-auto" /></a></p>
<p><strong>Profiling</strong> performance will take you places - <em>and at times</em> - the improvement process can spin off into quite interesting journeys often leading to <em>unexpected results. Performance tuning is one of significant parts for qryn development,</em> and we are extremely proud of being able to do it using <a target="_blank" href="https://qryn.dev">qryn</a> itself!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1725748680993/76aa317a-a0bc-41a2-bd0c-b83d310dfd11.gif" alt class="image--center mx-auto" /></p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><a target="_blank" href="https://blog.qryn.dev/pyroscope-qryn">Check our Pyroscope Continuous Profiling announcement </a>🔥🔥🔥🔥</div>
</div>

<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1725391051656/e5a773d2-d5ff-4ce5-b899-cd611ddb5770.png" alt class="image--center mx-auto" /></p>
<p>Today we'll share a small sample from our development experience in using <a target="_blank" href="https://go.dev/doc/pgo">PGO</a> to optimize the qryn <a target="_blank" href="https://github.com/metrico/otel-collector">Opentelemetry</a> collector performance. Through the article, we'll examine the effectiveness of <a target="_blank" href="https://go.dev/doc/pgo">PGO</a> and analyze the results of our profiling exercise using <a target="_blank" href="https://qryn.dev">qryn</a> to explore the practical implications of this optimization technique.</p>
<h3 id="heading-application-benchmark-setup">Application Benchmark Setup</h3>
<p>In order to perform our optimization and demonstrate the results, <em>we need an application to optimize</em>. The application should be under a constant load for a significant amount of time <em>(the longer, the better; let's say at least 20 minutes)</em>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1725388470542/04c28b7f-fc2c-4f16-bf97-ed18dc98d3e9.png" alt class="image--center mx-auto" /></p>
<p>We will use the <strong>Pyroscope</strong> client to collect the profile data and some readers to access the collected profiles.</p>
<p>We will run an <a target="_blank" href="https://github.com/metrico/otel-collector">Opentelemetry</a> collector accepting application profiles through the <strong>Pyroscope-compatible API</strong> and saving them directly into the <a target="_blank" href="https://qryn.dev">qryn</a> database.</p>
<p>The running <a target="_blank" href="https://github.com/metrico/otel-collector">Opentelemetry</a> collector features a <strong>Pyroscope</strong> extension that sends its own profiles to the accepting endpoint described earlier.</p>
<p><strong><em>Thus, in this specific example, the collector will profiles itself</em></strong> 🔥</p>
<p>Apart from the <a target="_blank" href="https://github.com/metrico/otel-collector">Opentelemetry</a> collector, we will leverage a <a target="_blank" href="https://qryn.dev">qryn</a> instance reading from the same database and a Grafana instance providing the user interface to read the saved profiles. <em>Let's start our profiling exercise!</em></p>
<p><img src="https://i.ytimg.com/vi/XtdpaLnVtAA/hq720.jpg?sqp=-oaymwEhCK4FEIIDSFryq4qpAxMIARUAAAAAGAElAADIQj0AgKJD&amp;rs=AOn4CLBFI6A9tpmeH8OV2vthPmsTWgRxSw" alt="20 Minutes Later | Spongebob Time Cards" /></p>
<h1 id="heading-pgo-optimization-of-the-collector">PGO optimization of the Collector</h1>
<p>This example optimization process will be quite simple:</p>
<ol>
<li><p>Wait for 10-20 minutes to collect a sufficient amount of <strong>pprof</strong> data.</p>
</li>
<li><p>Merge the collected <strong>pprof</strong> observations into one result pprof.</p>
</li>
<li><p>Add a <code>-pgo pgo.pprof</code> flag to the <code>go build</code> directive to build the optimized application.</p>
</li>
</ol>
<p>Let's begin by analyzing the outcome of our first step:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724952400402/f28be144-e616-41f6-9ff9-4bc9545849c2.png" alt class="image--center mx-auto" /></p>
<p>The results are quite clear and easy to understand:</p>
<ul>
<li><p>About 50% of the time was taken by the Go garbage collector (GC)</p>
</li>
<li><p><code>runtime.mca...</code> is likely the HTTP server</p>
</li>
<li><p><code>(4) go.opente</code> is the qryn exporter writing to the qryn database</p>
</li>
<li><p><a target="_blank" href="http://github.com/"><code>github.com/</code></a> is the Pyroscope client itself</p>
</li>
</ul>
<p>Next, let's try to combine the intermediate <strong>pprofs</strong> using the <a target="_blank" href="https://grafana.com/docs/pyroscope/latest/view-and-analyze-profile-data/profile-cli/"><strong>profilecli</strong></a> tool:</p>
<pre><code class="lang-bash">$ profilecli query merge \
  --from=<span class="hljs-string">'2024-08-27T15:37:45+03:00'</span> \
  --to=<span class="hljs-string">'2024-08-27T16:00:30+03:00'</span> \
  --url=http://localhost:3100 | head -n 10
level=info msg=<span class="hljs-string">"query aggregated profile from profile store"</span> url=http://localhost:3100 from=2024-08-27T15:37:45+03:00 to=2024-08-27T16:00:30+03:00 query={} <span class="hljs-built_in">type</span>=process_cpu:cpu:nanoseconds:cpu:nanoseconds
PeriodType: cpu nanoseconds
Period: 10000000
Time: 2024-08-27 15:37:55.600057479 +0300 EEST
Duration: 22m4
Samples:
samples/count cpu/nanoseconds
          4   40000000: 1 2 3 4 5 6 
          2   20000000: 7 8 9 10 11 12 13 
          2   20000000: 14 15 16 17 18 5 6 
          2   20000000: 19 20 21 22 23 24 25 26 27
$ profilecli query merge \
  --from=<span class="hljs-string">'2024-08-27T15:37:45+03:00'</span> \
  --to=<span class="hljs-string">'2024-08-27T16:00:30+03:00'</span> \
  --url=http://localhost:3100 \
  --output=pprof=./pgo.pprof
level=info msg=<span class="hljs-string">"query aggregated profile from profile store"</span> url=http://localhost:3100 from=2024-08-27T15:37:45+03:00 to=2024-08-27T16:00:30+03:00 query={} <span class="hljs-built_in">type</span>=process_cpu:cpu:nanoseconds:cpu:nanoseconds
$ ls -la | grep pgo.pprof
-rw-r--r--   1 user user     30324 сер 29 20:42 pgo.pprof
</code></pre>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><em>A fresh 30kB pprof file is generated by the above </em><strong><em>profilecli </em></strong><em>operation</em></div>
</div>

<p>Since we mostly use a <strong>dockerized opentelemetry</strong> collector to run our test we can simply add the <code>-pgo</code> flag to the <strong>Dockerfile</strong> and build it:</p>
<pre><code class="lang-bash">$ cat cmd/otel-collector/Dockerfile 
<span class="hljs-comment"># Builder stage</span>
FROM golang:1.22.1-alpine as builder
RUN apk --update add ca-certificates
<span class="hljs-comment"># ...</span>
RUN <span class="hljs-built_in">cd</span> cmd/otel-collector &amp;&amp; \
  go build -pgo=/src/pgo.pprof -tags timetzdata -o /out/otel-collector

<span class="hljs-comment"># Final stage</span>
FROM scratch

<span class="hljs-comment"># ...</span>
ENTRYPOINT [<span class="hljs-string">"/otel-collector"</span>]
CMD [<span class="hljs-string">"--config"</span>, <span class="hljs-string">"/etc/otel/config.yaml"</span>]

$ docker build \
  -t otel-collector:latest \
  -f cmd/otel-collector/Dockerfile .
[+] Building 61.5s (13/13) FINISHED                                                                                                                                                                                                      docker:default
 =&gt; [internal] load build definition from Dockerfile                                                                                                                                                                                               0.1s
 =&gt; =&gt; transferring dockerfile: 579B                                                                                                                                                                                                               0.0s
 =&gt; WARN: FromAsCasing: <span class="hljs-string">'as'</span> and <span class="hljs-string">'FROM'</span> keywords<span class="hljs-string">' casing do not match (line 2)                                                                                                                                                                     0.1s
 =&gt; [internal] load metadata for docker.io/library/golang:1.22.1-alpine                                                                                                                                                                            1.6s
...
 =&gt; =&gt; naming to docker.io/library/otel-collector:latest                                                                                                                                                                                           0.0s</span>
</code></pre>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Note: the full example Dockerfile can be <a target="_blank" href="https://github.com/metrico/otel-collector/blob/main/cmd/otel-collector/Dockerfile">found on our github repository</a></div>
</div>

<p>Let's repeat our test and wait for another 20 minutes and check the results...</p>
<p><img src="https://i.ytimg.com/vi/XtdpaLnVtAA/hq720.jpg?sqp=-oaymwEhCK4FEIIDSFryq4qpAxMIARUAAAAAGAElAADIQj0AgKJD&amp;rs=AOn4CLBFI6A9tpmeH8OV2vthPmsTWgRxSw" alt="20 Minutes Later | Spongebob Time Cards" /></p>
<h1 id="heading-results-of-the-optimization">Results of the Optimization</h1>
<p><em>According to most blog articles, PGO typically yields a 2-7% improvement.</em></p>
<p><em>Initially, we want to debunk how multiple iterations could potentially improve the application's performance several times (spoiler alert: they do not)</em></p>
<p>To find out we performed two iterations of the process and compared results:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724954887655/defee765-9626-469f-9e7c-d2f384f59ddb.png" alt class="image--center mx-auto" /></p>
<p>Both charts represent 20 minutes span of observation, with the application under constant load. The comparison between Base and First Round of optimization shows promising results: an improvement of <code>(1-(4.66/4.94))*100=5.67%</code>.</p>
<p>However, as suspected, the Second Round yielded less optimistic results:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724955280015/1a7240ce-cc4a-45ed-9405-f82db9577779.png" alt class="image--center mx-auto" /></p>
<p>🛑 The comparison of <strong>4.94s</strong> vs <strong>4.97s</strong> shows <code>(1-(4.94/4.97))*100=0.6%</code> downgrade</p>
<p>We can conclude that the second round performance is similar <em>(if not slightly worse)</em> to the baseline measurement. This easily demonstrates that there's definitely lots of value in PGO but also no such thing as infinite optimization and no speed of light :)</p>
<p>The most viable strategy for <strong>PGO</strong> usage:</p>
<ol>
<li><p>Provide a benchmark of the <em>unoptimized</em> Go application.</p>
</li>
<li><p>Observe performance using the <strong>Pyroscope</strong> client and <a target="_blank" href="https://qryn.dev"><strong>qryn</strong></a><strong>.</strong></p>
</li>
<li><p>Save the resulting <strong>pprof</strong> for <strong>PGO</strong> needs periodically.</p>
</li>
</ol>
<h1 id="heading-other-notices-during-the-comparison">Other notices during the comparison</h1>
<p>Contrary to my expectations based on articles about PGO, which suggested it would <em>"trim the noodles"</em> on the <strong>flamechart</strong> by in-lining some functions into their callers, it sometimes does quite the opposite:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724956122596/c6ae2e06-aa89-4fe4-b94d-1d042ea2cd69.png" alt class="image--center mx-auto" /></p>
<p><em>(left is baseline, right is optimized)</em></p>
<p>Next: let's take things a step further and examine the <strong>diff view</strong> supported in <a target="_blank" href="https://qryn.dev"><strong>qryn</strong></a> since the latest <strong>3.2.29</strong> version.</p>
<blockquote>
<p><mark>Hey, did we mention how we're using ONLY </mark> <a target="_blank" href="https://qryn.dev"><strong><mark>qryn</mark></strong></a> <mark>and </mark> <strong><mark>NOT Pyroscope</mark></strong> <mark>to run this test? 😏 We're so proud of our polyglot API supporting Continuous Profiling!</mark></p>
</blockquote>
<h2 id="heading-pyroscope-diff-view">Pyroscope diff view</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724957434439/b57a6c63-31bb-4c55-9637-dde574a7b726.png" alt class="image--center mx-auto" /></p>
<p>Well, obviously, the most improved parts are GC <em>(Garbage Collection)</em> and the <strong>runtime.mcall</strong> HTTP server. <strong>PGO</strong> definitely <em>"trimmed some noodles"</em> there (dark green color in the mid GC part). The rest of the chart needs further investigation.</p>
<p>The <strong>Pyroscope</strong> diff view is not very intuitive to read. The main downside I see is that they use percent comparison to represent color. Let's consider an example:</p>
<p>Suppose John, Mike, and I had 2 apples each. We had 6 apples overall, and each of us had 33% of all apples. Then one of my apples was "optimized" out. Now we have 5 apples. John and Mike have 40% each, and I have just 20%.</p>
<ul>
<li><p>Have my apples been optimized? Yes, and my part will be green on the chart.</p>
</li>
<li><p>Have John's and Mike's apples been downgraded? No, they still have 2 apples each. But their parts will be red because their percent stake in increased by 10%.</p>
</li>
</ul>
<p>With this in mind, we should read the diff view as follows:</p>
<ul>
<li><p>green is definitely green</p>
</li>
<li><p>bright red is definitely bright red</p>
</li>
<li><p>faded red may be grey or green</p>
</li>
<li><p>grey may be green</p>
</li>
</ul>
<p>During the comparison process, at some point, I came up with an average determiner of the profile:<br /><code>&lt;value of a sample in ns&gt;/&lt;time between first and last merged observations&gt;</code>.</p>
<p>This would have the measurement of <code>ns/sec</code> and would be a more descriptive representation of the difference between profiles. However, it's quite complicated to calculate at this point.</p>
<p>During the comparison process at some point I came to the average determiner of the profile which is<br /><code>&lt;value of a sample in ns&gt;/&lt;time between first and last merged observations&gt;</code></p>
<p>That would have the measurement of <code>ns/sec</code> and will be a bit more descriptive representation of difference between profiles. But it's quite complicated to calculate at this point.</p>
<p>Another approach would be to select the same amount of time <em>(as I did in the explorer before the diff view chapter)</em> and compare the absolute values:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724958587626/11119d67-b7a6-4550-9980-f02d76fdb1d2.png" alt class="image--center mx-auto" /></p>
<p>As you can see here, the absolute difference of the sample is 0ns. But the sample is red due to the 8% difference. It's also worth mentioning that this approach is quite challenging because selection with a mouse without a time picker is never precise.</p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>The Profile-Guided Optimization (PGO) tool offers a relatively straightforward method to achieve some improvements in application efficiency. The experiments with the OpenTelemetry collector have shown several key insights:</p>
<ol>
<li><p><strong>Performance Gains</strong>: The initial round of PGO optimization resulted in a 5.67% improvement in performance. It is consistent within the average 2-7% range reported in literature.</p>
</li>
<li><p><strong>Diminishing Returns</strong>: The second round of optimization showed no additional improvement. This suggests that a single, well-executed PGO pass may be sufficient for most applications.</p>
</li>
<li><p><strong>Visualization Challenges</strong>: While the Pyroscope diff view in Grafana is valuable for analyzing optimizations, it needs some basic training to be read properly. This emphasizes the need for multiple analysis approaches, including absolute value comparisons over fixed time intervals.</p>
</li>
<li><p><strong>Integration with CI/CD</strong>: To fully leverage PGO, it should be integrated into the continuous integration and deployment pipeline, with periodic re-optimization (e.g., every few weeks) to adapt to changing usage patterns.</p>
</li>
<li><p><strong>Future of Profiling Tools</strong>: The limitations observed in current visualization tools, such as Grafana "<strong>Explore Profiles</strong>" plugin, point to opportunities for improvement in profiling and analysis tools, which could further enhance the effectiveness of techniques like PGO.</p>
</li>
</ol>
<p>In conclusion, while PGO is a "low hanging fruit" to gain a few percentage points of performance and represents a valuable tool in the Go developer's toolkit. As profiling tools and visualization techniques continue to evolve, the process of applying and analyzing PGO optimizations is likely to become even more accessible and insightful for development teams.</p>
<p><mark>And the best part - all you need is </mark> <a target="_blank" href="https://qryn.dev"><strong><mark>qryn</mark></strong></a> <mark>😏 the polyglot observability stack</mark></p>
<h2 id="heading-ready-for-polyglot-observability">Ready for Polyglot Observability?</h2>
<p><em>Logs, Metrics, Traces and Profiles. Your data, your way.</em></p>
<p>You can run <a target="_blank" href="https://qryn.dev"><strong>qryn</strong></a> self-hosted or managed by <a target="_blank" href="https://qryn.cloud"><strong>qryn.cloud</strong></a></p>
<p><a target="_blank" href="https://qryn.dev"><img src="https://github.com/metrico/qryn-docs/assets/1423657/a5164f98-d3ed-4638-afe5-c87d252c74af" alt /></a></p>
]]></content:encoded></item><item><title><![CDATA[🐣 Introducing: chsql for DuckDB]]></title><description><![CDATA[TLDR: DuckDB extension providing ClickHouse SQL Dialect Macros

Prequel
Our readers know this has been a ClickHouse centric blog for quite some time. Together we've documented our journey with qryn - the first polyglot observability stack build on to...]]></description><link>https://blog.gigapipe.com/introducing-chsql-for-duckdb</link><guid isPermaLink="true">https://blog.gigapipe.com/introducing-chsql-for-duckdb</guid><category><![CDATA[duckdb-extensions]]></category><category><![CDATA[quackpipe]]></category><category><![CDATA[duckDB]]></category><category><![CDATA[ClickHouse]]></category><category><![CDATA[SQL]]></category><category><![CDATA[chsql]]></category><category><![CDATA[motherduck]]></category><category><![CDATA[chdb]]></category><dc:creator><![CDATA[Lorenzo Mangani]]></dc:creator><pubDate>Thu, 11 Jul 2024 07:58:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1720650702774/69d1fc4c-aee3-4dc9-887b-54de9d1710b7.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><img src="https://private-user-images.githubusercontent.com/1423657/346273832-144dc202-f88a-4a2b-903d-51e30be75f6a.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjA2MzgzMzEsIm5iZiI6MTcyMDYzODAzMSwicGF0aCI6Ii8xNDIzNjU3LzM0NjI3MzgzMi0xNDRkYzIwMi1mODhhLTRhMmItOTAzZC01MWUzMGJlNzVmNmEucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDcxMCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDA3MTBUMTkwMDMxWiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZGY2OWNhNmMwYjQwMzViZThmMjA4ZDJhYTczODdlY2Y0ZmYwNDdlNWFmY2ZmMTE3NmY2MWJmMmFhNzU5YmU4ZiZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ.eiuAGea-cmRHRg3Vfv8QZgf4P4xWAbjPPktNaZHIlRo" alt /></p>
<blockquote>
<p><strong>TLDR: DuckDB extension providing ClickHouse SQL Dialect Macros</strong></p>
</blockquote>
<h3 id="heading-prequel">Prequel</h3>
<p>Our readers know this has been a <strong>ClickHouse</strong> centric blog for quite some time. Together we've documented our journey with <strong>qryn</strong> - <em>the first polyglot observability stack build on top of Clickhouse</em> - and on the side we built a few frankenstein serverless embedded Clickhouse series, later even joining efforts founding <em>chdb - quickly killed by world class crooks.</em><a target="_blank" href="https://blog.qryn.dev/clickhouse-duckdb"><em>We also experimented lots with DuckDB</em></a></p>
<h3 id="heading-first-quackhttpsgithubcommetricoquackpipe"><a target="_blank" href="https://github.com/metrico/quackpipe">First Quack</a> ⭐</h3>
<p>Once the <em>chdb adventure aborted</em> we switched focus on <a target="_blank" href="https://github.com/metrico/quackpipe"><strong>Quackpipe</strong></a>, a fast and tiny serverless OLAP API powered by <strong>DuckDB</strong> and emulating the <strong>ClickHouse HTTP API</strong> with basic format compatibility and shipping with the same play interface, session persistence and authentication. <strong>Quackpipe</strong> is double-face and also works as a FIFO processor and <a target="_blank" href="https://blog.qryn.dev/clickhouse-duckdb">ClickHouse UDFs to run DuckDB queries</a>.</p>
<p>🔥 <a target="_blank" href="https://quackpipe.fly.dev">Curious? Try running a serverless query</a> using our Fly.io <strong>public demo</strong></p>
<h3 id="heading-different-sql-strokes">Different SQL Strokes</h3>
<p>Now <strong>Quackpipe</strong> speaks the <strong>DuckDB SQL</strong> language - <em>which is amazing</em> - but our audience are <em>ClickHouse refugees who spent years mastering language conventions just like ourselves</em> - <em>and some of those functions are actually good and useful.</em></p>
<p>Our initial solution was <em>loading a list of ClickHouse SQL aliases at startup time but this approach was slow, fragile and quite hard to maintain, update and embed.</em></p>
<pre><code class="lang-sql"><span class="hljs-comment">-- Type conversion macros</span>
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">OR</span> <span class="hljs-keyword">REPLACE</span> MACRO toString(expr) <span class="hljs-keyword">AS</span> <span class="hljs-keyword">CAST</span>(expr <span class="hljs-keyword">AS</span> <span class="hljs-built_in">VARCHAR</span>);
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">OR</span> <span class="hljs-keyword">REPLACE</span> MACRO toInt8(expr) <span class="hljs-keyword">AS</span> <span class="hljs-keyword">CAST</span>(expr <span class="hljs-keyword">AS</span> <span class="hljs-built_in">INT8</span>);
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">OR</span> <span class="hljs-keyword">REPLACE</span> MACRO toInt16(expr) <span class="hljs-keyword">AS</span> <span class="hljs-keyword">CAST</span>(expr <span class="hljs-keyword">AS</span> INT16);
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">OR</span> <span class="hljs-keyword">REPLACE</span> MACRO toInt32(expr) <span class="hljs-keyword">AS</span> <span class="hljs-keyword">CAST</span>(expr <span class="hljs-keyword">AS</span> INT32);
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">OR</span> <span class="hljs-keyword">REPLACE</span> MACRO toInt64(expr) <span class="hljs-keyword">AS</span> <span class="hljs-keyword">CAST</span>(expr <span class="hljs-keyword">AS</span> INT64);
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">OR</span> <span class="hljs-keyword">REPLACE</span> MACRO toInt128(expr) <span class="hljs-keyword">AS</span> <span class="hljs-keyword">CAST</span>(expr <span class="hljs-keyword">AS</span> INT128);
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">OR</span> <span class="hljs-keyword">REPLACE</span> MACRO toInt256(expr) <span class="hljs-keyword">AS</span> <span class="hljs-keyword">CAST</span>(expr <span class="hljs-keyword">AS</span> HUGEINT);
<span class="hljs-comment">-- Type conversion with default values</span>
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">OR</span> <span class="hljs-keyword">REPLACE</span> MACRO toInt8OrZero(expr) <span class="hljs-keyword">AS</span> <span class="hljs-keyword">COALESCE</span>(<span class="hljs-keyword">TRY_CAST</span>(expr <span class="hljs-keyword">AS</span> <span class="hljs-built_in">INT8</span>), <span class="hljs-number">0</span>);
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">OR</span> <span class="hljs-keyword">REPLACE</span> MACRO toInt16OrZero(expr) <span class="hljs-keyword">AS</span> <span class="hljs-keyword">COALESCE</span>(<span class="hljs-keyword">TRY_CAST</span>(expr <span class="hljs-keyword">AS</span> INT16), <span class="hljs-number">0</span>);
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">OR</span> <span class="hljs-keyword">REPLACE</span> MACRO toInt32OrZero(expr) <span class="hljs-keyword">AS</span> <span class="hljs-keyword">COALESCE</span>(<span class="hljs-keyword">TRY_CAST</span>(expr <span class="hljs-keyword">AS</span> INT32), <span class="hljs-number">0</span>);
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">OR</span> <span class="hljs-keyword">REPLACE</span> MACRO toInt64OrZero(expr) <span class="hljs-keyword">AS</span> <span class="hljs-keyword">COALESCE</span>(<span class="hljs-keyword">TRY_CAST</span>(expr <span class="hljs-keyword">AS</span> INT64), <span class="hljs-number">0</span>);
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">OR</span> <span class="hljs-keyword">REPLACE</span> MACRO toInt128OrZero(expr) <span class="hljs-keyword">AS</span> <span class="hljs-keyword">COALESCE</span>(<span class="hljs-keyword">TRY_CAST</span>(expr <span class="hljs-keyword">AS</span> INT128), <span class="hljs-number">0</span>);
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">OR</span> <span class="hljs-keyword">REPLACE</span> MACRO toInt256OrZero(expr) <span class="hljs-keyword">AS</span> <span class="hljs-keyword">COALESCE</span>(<span class="hljs-keyword">TRY_CAST</span>(expr <span class="hljs-keyword">AS</span> HUGEINT), <span class="hljs-number">0</span>);
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">OR</span> <span class="hljs-keyword">REPLACE</span> MACRO toInt8OrNull(expr) <span class="hljs-keyword">AS</span> <span class="hljs-keyword">TRY_CAST</span>(expr <span class="hljs-keyword">AS</span> <span class="hljs-built_in">INT8</span>);
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">OR</span> <span class="hljs-keyword">REPLACE</span> MACRO toInt16OrNull(expr) <span class="hljs-keyword">AS</span> <span class="hljs-keyword">TRY_CAST</span>(expr <span class="hljs-keyword">AS</span> INT16);
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">OR</span> <span class="hljs-keyword">REPLACE</span> MACRO toInt32OrNull(expr) <span class="hljs-keyword">AS</span> <span class="hljs-keyword">TRY_CAST</span>(expr <span class="hljs-keyword">AS</span> INT32);
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">OR</span> <span class="hljs-keyword">REPLACE</span> MACRO toInt64OrNull(expr) <span class="hljs-keyword">AS</span> <span class="hljs-keyword">TRY_CAST</span>(expr <span class="hljs-keyword">AS</span> INT64);
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">OR</span> <span class="hljs-keyword">REPLACE</span> MACRO toInt128OrNull(expr) <span class="hljs-keyword">AS</span> <span class="hljs-keyword">TRY_CAST</span>(expr <span class="hljs-keyword">AS</span> INT128);
<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">OR</span> <span class="hljs-keyword">REPLACE</span> MACRO toInt256OrNull(expr) <span class="hljs-keyword">AS</span> <span class="hljs-keyword">TRY_CAST</span>(expr <span class="hljs-keyword">AS</span> HUGEINT);
<span class="hljs-comment">-- and so on and on....</span>
</code></pre>
<p>Luckily something much better was at the horizon....</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720639202069/d232914b-1c8e-408c-b106-c6dab8c99f97.png" alt class="image--center mx-auto" /></p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Is this a dream? SQL Macros are what we need to start and we can add any missing features or format natively in C++ when we need to....</div>
</div>

<h3 id="heading-duckdb-extensionshttpsduckdborgdocsextensionsoverviewhtml"><a target="_blank" href="https://duckdb.org/docs/extensions/overview.html">DuckDB Extensions</a> 🦆</h3>
<p><strong>DuckDB</strong> has a flexible extension mechanism that allows for dynamically loading extensions on all supported architectures, extending <strong>DuckDB</strong> functionality by providing support for additional file formats, introducing new types, and domain-specific functionality. <em>What would we do without JSON, HTTPFS, Arrow, etc?</em></p>
<p>And while <strong>DuckDB Labs</strong> could have selfishly kept extension super complex they decided to do the opposite and invited every developer to the <strong>party of the year</strong>!</p>
<h3 id="heading-duckdb-community-extensionshttpsduckdborg20240705community-extensions"><a target="_blank" href="https://duckdb.org/2024/07/05/community-extensions">DuckDB Community Extensions</a> 🦆🦆🦆</h3>
<blockquote>
<p><strong><em>TL;DR: DuckDB extensions can now be published via the</em></strong><a target="_blank" href="https://github.com/duckdb/community-extensions"><strong><em>DuckDB Community Extensions repository</em></strong></a><strong><em>.</em></strong><a target="_blank" href="https://github.com/duckdb/community-extensions"><strong><em>The repository makes it easier for us</em></strong></a><strong><em>ers to install extensions using the</em></strong><code>INSTALL ⟨extension name⟩ FROM community</code> syntax. Extension developers avoid the burdens of compilation and distribution.</p>
</blockquote>
<p><mark>This it </mark> <strong><mark>not </mark></strong> <mark>marketing</mark>. <strong>The full ecosystem is ready to use with working examples, actions to build for all platforms and distribution for community extensions!</strong></p>
<p><em>This is how it's done. Competition is none.</em></p>
<p><img src="https://media1.giphy.com/media/0NwSQpGY6ipgOSt8LL/giphy.gif?cid=6c09b9521djwg58dqkckl7xk380m3snbwqqrv9pdopzremsw&amp;ep=v1_gifs_search&amp;rid=giphy.gif&amp;ct=g" alt /></p>
<p><mark>New plan: </mark> <em><mark>Quackpipe will use an extension to speak "ClickHouse" friendly.</mark></em></p>
<h3 id="heading-clickhouse-sql-extensionhttpscommunity-extensionsduckdborgextensionschsqlhtml">👋 <a target="_blank" href="https://community-extensions.duckdb.org/extensions/chsql.html">ClickHouse SQL Extension</a></h3>
<p>Thanks to the fantastic examples a few hours later the <a target="_blank" href="https://community-extensions.duckdb.org/extensions/chsql.html"><strong>chsql</strong></a> extension for DuckDB was already born implementing a small but growing number of native macros using <em><mark>ClickHouse SQL syntax transpiled to DuckDB SQL and Lambdas</mark></em>, making it easier to transition knowledge, users and scripts between the two database systems.</p>
<p>Publishing the extension was so easy it felt like cheating. It's quack magic.</p>
<p><a target="_blank" href="https://quackpipe.fly.dev"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720640535933/46a0d950-e3ca-4359-ba5d-3f905858b9a2.png" alt class="image--center mx-auto" /></a></p>
<h3 id="heading-surprise-motherducker">Surprise, MotherDucker!</h3>
<p><strong>Community</strong> is where <strong>DuckDB</strong> really shines. After years of working with Vendors trying to kill any opensource Community effort this ecosystem <strong><em>feels different</em></strong>.</p>
<p>Respect to everyone at DuckDB and MotherDuck for doing such an amazing job.</p>
<p><em><mark>UPDATE: We're excited to be part of the </mark></em> <strong><em><mark>MotherDuck Startup Program</mark>*</em></strong><mark>! 🦆</mark>*</p>
<p><img src="https://media4.giphy.com/media/v1.Y2lkPTc5MGI3NjExaDE2aDk4NDJmcDBkOTJ6bzBxdWhmaGJrYzE5ejBjcGo3MW1sbWN2byZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/4JZA2x7GsVFeTbLKlz/giphy.webp" alt /></p>
<p><em>Kudos: As soon as we started working on the extension and before we could even ask several Ducks flocked in and kindly helped us overcome a few initial issues, demonstrating how much they respect users and value developer interactions.<strong>**A++</strong></em></p>
<p><em>For the first time after years of attempting to work with crippled UDFs in ClickHouse and dealing with the monster size of the project, we see some light ahead in OLAP.</em></p>
<p><strong>So - Expect lots more DuckDB content on the blog as the adventure continues!</strong></p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong>Lesson Learned: Always prefer Motherduckers to Mother%uckers</strong></div>
</div>

<hr />
<h2 id="heading-sql-hackers-join-us">⭐⭐⭐ SQL Hackers, Join us! ⭐⭐⭐</h2>
<p><mark>This is just the beginning of a long journey. </mark> <a target="_blank" href="https://github.com/lmangani/duckdb-extension-clickhouse-sql/discussions/7"><mark>To succeed we'll need your help</mark></a><mark>.</mark></p>
<p>If you're a <strong>ClickHouse</strong> or <strong>DuckDB SQL wizard*</strong>(or just have lots of SQL patience)* you can join the fun and contribute by adding, fixing or extending supported macros:</p>
<ol>
<li><p>Find a <strong>ClickHouse</strong> function you are interested into from the <a target="_blank" href="https://clickhouse.com/docs/en/sql-reference/functions">functions list</a></p>
</li>
<li><p>Find any <a target="_blank" href="https://duckdb.org/docs/sql">DuckDB functions</a> offering viable methods to alias the target function</p>
</li>
<li><p>Create your macro and extend to neighboring functions with similar scope.</p>
</li>
<li><p>Test and <a target="_blank" href="https://github.com/lmangani/duckdb-extension-clickhouse-sql">Submit your contribution</a>. We'll do the coding if needed.</p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[🔺eBPF Observability with beyla + qryn]]></title><description><![CDATA[Grafana Beyla is a new vendor agnostic, eBPF-based, OpenTelemetry/Prometheus application auto-instrumentation tool, which lets you easily get started with Application Observability. Within Beyla eBPF is used to automatically inspect application execu...]]></description><link>https://blog.gigapipe.com/ebpf-observability-with-beyla-qryn</link><guid isPermaLink="true">https://blog.gigapipe.com/ebpf-observability-with-beyla-qryn</guid><category><![CDATA[eBPF]]></category><category><![CDATA[observability]]></category><category><![CDATA[qryn]]></category><category><![CDATA[distributed tracing]]></category><category><![CDATA[OpenTelemetry]]></category><dc:creator><![CDATA[Lorenzo Mangani]]></dc:creator><pubDate>Wed, 10 Jul 2024 18:59:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1720637714022/88ef0c08-510e-4512-b391-6f86f686f82d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://grafana.com/docs/beyla/latest/"><img src="https://grafana.com/media/docs/grafana-cloud/beyla/beyla-logo.png" alt="Grafana Beyla Logo" /></a></p>
<p><strong>Grafana Beyla</strong> is a new vendor agnostic, <strong>eBPF</strong>-based, <strong>OpenTelemetry/Prometheus</strong> application auto-instrumentation tool, which lets you easily get started with Application Observability. Within Beyla eBPF is used to automatically inspect application executables and the OS networking layer, allowing us to capture essential application observability events for HTTP/S and gRPC services.</p>
<p>From captured eBPF events, Beyla produces <strong>Opentelemetry</strong> trace spans and <strong>Rate-Errors-Duration (RED)</strong> metrics, without any modifications to your application code or configuration and of course - <em>compatible with qryn for ingestion and usage.</em></p>
<h3 id="heading-using-beyla">Using Beyla</h3>
<p><strong>Beyla</strong> supports a wide range of programming languages <em>(Go, Java, .NET, NodeJS, Python, Ruby, Rust, etc.) and can be used in parallel with other existing signals.</em></p>
<p>To get started, refer to the <a target="_blank" href="https://grafana.com/docs/beyla/latest/quickstart/">auto-instrumentation QuickStart</a> in Grafana Beyla.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1712651744606/c595223d-7380-4da0-b60e-e6d25ddaae72.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-sending-data-from-beyla-to-qryn">Sending data from Beyla to qryn</h3>
<p><strong>Beyla</strong> was designed for <strong>Grafana LGTM</strong> which means its <mark>drop-in compatible with </mark> <strong><mark>qryn</mark></strong> when it comes to shipping traces, metrics and even logs using native API formats, thanks to the <em>polyglot all-in-one API approach</em> implemented by the qryn stack.</p>
<p>And since qryn supports direct ingestion of OTEL traces, configuring <strong>Beyla</strong> with the qryn <strong>OTEL</strong> endpoint in Docker is as easy as it gets! Just remember to configure the process <strong>PID, BEYLA_OPEN_PORT</strong> &amp; <strong>BEYLA_SERVICE_NAME</strong> parameters <em>to match your target application process</em> (or docker container) before running:</p>
<pre><code class="lang-bash">docker run --rm --pid=<span class="hljs-string">"container:clickhouse-server"</span> 
-e BEYLA_OPEN_PORT=8123<span class="hljs-_">-e</span> BEYLA_SERVICE_NAME=<span class="hljs-string">"clickhouse"</span> 
-e OTEL_EXPORTER_OTLP_PROTOCOL=<span class="hljs-string">"http/protobuf"</span> 
-e OTEL_EXPORTER_OTLP_ENDPOINT=<span class="hljs-string">"http://qryn:3100"</span>  
--privileged grafana/beyla:latest
</code></pre>
<p><em><mark>That's all it takes! We're ready to launch our Beyla instance and validate!</mark></em></p>
<p>A few seconds later your process tracing will be visible in <strong>qryn/tempo</strong> by searching for the <strong>BEYLA_SERVICE_NAME</strong> configured in the previous step:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1712583192041/d18820e5-36ed-4051-872d-bc9e166165e2.png" alt class="image--center mx-auto" /></p>
<p>That's just the beginning of the journey and there's so much more to discover and use when it comes to <strong>eBPF</strong> and <strong>qryn</strong> combined. Post your comments and feedback!</p>
<p><a target="_blank" href="https://github.com/metrico/qryn"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1720637662372/d2cab846-ce62-40c6-9993-2b3af26a0118.png" alt class="image--center mx-auto" /></a></p>
]]></content:encoded></item><item><title><![CDATA[🐙 Using Grafana alloy with qryn]]></title><description><![CDATA[Grafana just announced Alloy a new flexible, high performance, vendor-neutral distribution of the Opentelemetry (OTel) Collector. Since Alloy is compatible with the most popular open source observability standards such as Opentelemetry, Prometheus an...]]></description><link>https://blog.gigapipe.com/qryn-with-alloy</link><guid isPermaLink="true">https://blog.gigapipe.com/qryn-with-alloy</guid><category><![CDATA[observability]]></category><category><![CDATA[Alloy]]></category><category><![CDATA[qryn]]></category><category><![CDATA[#prometheus]]></category><category><![CDATA[loki]]></category><category><![CDATA[k8s]]></category><dc:creator><![CDATA[Lorenzo Mangani]]></dc:creator><pubDate>Wed, 10 Apr 2024 21:22:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1712781203676/209265d8-9059-4ef9-ba32-ae6bc451b309.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://github.com/grafana"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1712782103057/f51fcea1-c720-4def-b623-566483b73756.png" alt /></a></p>
<p>Grafana just announced <a target="_blank" href="https://github.com/grafana/alloy"><strong>Alloy</strong></a> a new flexible, high performance, vendor-neutral distribution of the <a target="_blank" href="https://opentelemetry.io/ecosystem/distributions/">Opentelemetry</a> (OTel) Collector. Since Alloy is compatible with the most popular open source observability standards such as <em>Opentelemetry, Prometheus and Loki -</em> it is also <mark>fully compatible our of the box with </mark> <a target="_blank" href="https://github.com/metrico/qryn"><strong><mark>qryn</mark></strong></a><strong>!</strong></p>
<p><strong><em>That's our <mark>polyglot </mark> magic making Observability less of a pain for everyone!</em></strong></p>
<p><a target="_blank" href="https://github.com/metrico/qryn"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1712780963395/68bf4c7a-3dd7-4704-a7a4-5ba82dbd4732.png" alt class="image--center mx-auto" /></a></p>
<p><a target="_blank" href="https://github.com/grafana/alloy"><strong>Alloy</strong></a> is a vendor-neutral telemetry collector. This means that Alloy doesn’t enforce a specific deployment topology but can work in multiple scenarios acting as a metrics scraper, logs scraper, Opentelemetry receiver and more - with dynamic routing.</p>
<h2 id="heading-the-alloy-pipeline">💦 The Alloy Pipeline</h2>
<p><em>Just like most collectors, Alloy offers its functionality in different stages:</em></p>
<p><strong>1) Collect</strong></p>
<p>Alloy uses more than 120 components to collect telemetry data from applications, databases, and Opentelemetry collectors. Alloy supports collection using multiple ecosystems, including Opentelemetry and Prometheus. Telemetry data can be either pushed to Alloy, or Alloy can pull it (scrape) from your data sources.</p>
<p><strong>2) Transform</strong></p>
<p>Alloy processes data and transforms it for sending. You can use transformations to inject extra metadata into telemetry or filter out unwanted data.</p>
<p><strong>3) Write</strong></p>
<p>Alloy sends data to Opentelemetry-compatible databases or collectors such as Grafana LGTM or <a target="_blank" href="https://github.com/metrico/qryn">qryn</a>. Alloy can also write alerting rules in compatible databases.</p>
<p><a target="_blank" href="https://github.com/metrico/qryn"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1712781147736/fbfd1d32-f717-415b-aae1-4f4297f8722a.png" alt class="image--center mx-auto" /></a></p>
<h2 id="heading-installation">⚙️ Installation</h2>
<p><a target="_blank" href="https://grafana.com/docs/alloy/latest/get-started/install/">Installi Alloy on your system</a> following the official <a target="_blank" href="https://grafana.com/docs/alloy/latest/get-started/install/">documentation</a></p>
<h3 id="heading-run-a-linux-docker-container"><strong>Run a Linux Docker container</strong></h3>
<p>To run Alloy examples in Docker use the following command in a terminal:</p>
<pre><code class="lang-bash">docker run \
  -v &lt;CONFIG_FILE_PATH&gt;:/etc/alloy/config.alloy \
  -p 12345:12345 \
  grafana/alloy:latest \
    run --server.http.listen-addr=0.0.0.0:12345 --storage.path=/var/lib/alloy/data \
    /etc/alloy/config.alloy
</code></pre>
<p>Replace the following:</p>
<ul>
<li><code>&lt;CONFIG_FILE_PATH&gt;</code>: The path of the configuration file on your host system.</li>
</ul>
<h3 id="heading-data-collection">⚠️ <strong>Data collection</strong> ⚠️</h3>
<p><mark>By default, Grafana Alloy sends anonymous </mark> <em><mark>(but uniquely identifiable)</mark></em> <mark> usage information from your Alloy instance to Grafana Labs.</mark> Use the <code>-disable-reporting</code> command line flag to disable the reporting and opt-out from this annoyance.</p>
<h2 id="heading-alloy-examples-for-qryn">⚙️ Alloy Examples for qryn</h2>
<p>Here are some generic observability examples combining <a target="_blank" href="https://github.com/metrico/qryn"><strong>Alloy</strong> and <strong>qryn</strong></a><strong>:</strong></p>
<h3 id="heading-prometheus-scraper">🔶 Prometheus Scraper</h3>
<p>Scrape Prometheus Metrics and forward them to qryn for ingestion</p>
<pre><code class="lang-bash">prometheus.scrape <span class="hljs-string">"prometheus"</span> {
  targets = [{
    __address__ = <span class="hljs-string">"localhost:12345"</span>,
  }]
  forward_to     = [prometheus.remote_write.default.receiver]
  job_name       = <span class="hljs-string">"prometheus"</span>
  scrape_timeout = <span class="hljs-string">"45s"</span>
}

prometheus.remote_write <span class="hljs-string">"default"</span> {
  endpoint {
    name = <span class="hljs-string">"qryn"</span>
    url  = <span class="hljs-string">"https://qryn:3100/api/prom/push"</span>

    basic_auth {
      username = <span class="hljs-string">"USERNAME"</span>
      password = <span class="hljs-string">"PASSWORD"</span>
    }

    queue_config {
      capacity             = 2500
      max_shards           = 200
      max_samples_per_send = 500
    }

    metadata_config {
      max_samples_per_send = 500
    }
  }
}
</code></pre>
<h3 id="heading-log-scraper">🔶 Log Scraper</h3>
<p>Scrape Logs from any system and forward them to qryn for ingestion</p>
<pre><code class="lang-bash">local.file_match <span class="hljs-string">"example"</span> {
    path_targets = [{
        __address__ = <span class="hljs-string">"localhost"</span>,
        __path__    = <span class="hljs-string">"/var/log/*.log"</span>,
    }]
}

loki.source.file <span class="hljs-string">"example"</span> {
    targets    = local.file_match.example.targets
    forward_to = [loki.write.default.receiver]
}

loki.write <span class="hljs-string">"default"</span> {
    endpoint {
        url = <span class="hljs-string">"http://qryn:3100/loki/api/v1/push"</span>
    }
    external_labels = {}
}
</code></pre>
<h3 id="heading-otel-collector-for-qryn">🔶 OTel Collector for qryn</h3>
<p>Collect and Forward Opentelemetry protocols to qryn</p>
<pre><code class="lang-bash">otelcol.receiver.otlp <span class="hljs-string">"default"</span> {
    grpc { }

    http { }

    output {
        metrics = [otelcol.processor.memory_limiter.default.input]
        logs    = [otelcol.processor.memory_limiter.default.input]
        traces  = [otelcol.processor.memory_limiter.default.input]
    }
}

otelcol.processor.memory_limiter <span class="hljs-string">"default"</span> {
    check_interval   = <span class="hljs-string">"1s"</span>
    limit_percentage = 90

    output {
        metrics = [otelcol.exporter.otlp.default.input]
        logs    = [otelcol.exporter.otlp.default.input]
        traces  = [otelcol.exporter.otlp.default.input]
    }
}

otelcol.exporter.otlp <span class="hljs-string">"default"</span> {
    client {
        endpoint = <span class="hljs-string">"qryn:3100"</span>
    }
}
</code></pre>
<h3 id="heading-tempo-collector-with-service-graphhttpsgrafanacomdocsalloylatestreferencecomponentsotelcolconnectorservicegraph">🔶 Tempo Collector with <a target="_blank" href="https://grafana.com/docs/alloy/latest/reference/components/otelcol.connector.servicegraph/">Service Graph</a></h3>
<pre><code class="lang-bash">otelcol.receiver.otlp <span class="hljs-string">"default"</span> {
  grpc {
    endpoint = <span class="hljs-string">"0.0.0.0:4320"</span>
  }

  output {
    traces  = [otelcol.connector.servicegraph.default.input,otelcol.exporter.otlp.qryn_tempo.input]
  }
}

otelcol.connector.servicegraph <span class="hljs-string">"default"</span> {
  dimensions = [<span class="hljs-string">"http.method"</span>]
  output {
    metrics = [otelcol.exporter.prometheus.default.input]
  }
}

otelcol.exporter.prometheus <span class="hljs-string">"default"</span> {
  forward_to = [prometheus.remote_write.qryn.receiver]
}

prometheus.remote_write <span class="hljs-string">"qryn"</span> {
  endpoint {
    url = <span class="hljs-string">"https://qryn:3100/api/prom/push"</span>

    basic_auth {
      username = env(<span class="hljs-string">"QRYN_USERNAME"</span>)
      password = env(<span class="hljs-string">"QRYN_PASSWORD"</span>)
    }
  }
}

otelcol.exporter.otlp <span class="hljs-string">"qryn_tempo"</span> {
  client {
    endpoint = <span class="hljs-string">"https://qryn:3100"</span>
    auth     = otelcol.auth.basic.qryn_tempo.handler
  }
}

otelcol.auth.basic <span class="hljs-string">"qryn_tempo"</span> {
  username = env(<span class="hljs-string">"QRYN_USERNAME"</span>)
  password = env(<span class="hljs-string">"QRYN_PASSWORD"</span>)
}
</code></pre>
<h3 id="heading-k8s-operator-for-qryn">🔶 K8s Operator for qryn</h3>
<pre><code class="lang-bash">// <span class="hljs-built_in">read</span> the credentials secret <span class="hljs-keyword">for</span> remote_write authorization
remote.kubernetes.secret <span class="hljs-string">"credentials"</span> {
  namespace = <span class="hljs-string">"monitoring"</span>
  name = <span class="hljs-string">"primary-credentials-metrics"</span>
}

prometheus.remote_write <span class="hljs-string">"primary"</span> {
    endpoint {
        url = <span class="hljs-string">"https://qryn:3100/api/v1/push"</span>
        basic_auth {
            username = nonsensitive(remote.kubernetes.secret.credentials.data[<span class="hljs-string">"username"</span>])
            password = remote.kubernetes.secret.credentials.data[<span class="hljs-string">"password"</span>]
        }
    }
}

prometheus.operator.podmonitors <span class="hljs-string">"primary"</span> {
    forward_to = [prometheus.remote_write.primary.receiver]
    // leave out selector to find all podmonitors <span class="hljs-keyword">in</span> the entire cluster
    selector {
        match_labels = {instance = <span class="hljs-string">"primary"</span>}
    }
}

prometheus.operator.servicemonitors <span class="hljs-string">"primary"</span> {
    forward_to = [prometheus.remote_write.primary.receiver]
    // leave out selector to find all servicemonitors <span class="hljs-keyword">in</span> the entire cluster
    selector {
        match_labels = {instance = <span class="hljs-string">"primary"</span>}
    }
}
</code></pre>
<h3 id="heading-k8s-pods-scraper-for-qryn">🔶 K8s Pods Scraper for qryn</h3>
<pre><code class="lang-bash">// Get our API key from disk.
//
// This component has an exported field called <span class="hljs-string">"content"</span>, holding the content
// of the file.
//
// local.file.api_key will watch the file and update its exports any time the
// file changes.
local.file <span class="hljs-string">"api_key"</span> {
  filename  = <span class="hljs-string">"/var/data/secrets/api-key"</span>

  // Mark this file as sensitive to prevent its value from being shown <span class="hljs-keyword">in</span> the
  // UI.
  is_secret = <span class="hljs-literal">true</span>
}

// Create a prometheus.remote_write component, <span class="hljs-built_in">which</span> other components can send
// metrics to.
//
// This component exports a <span class="hljs-string">"receiver"</span> value, <span class="hljs-built_in">which</span> can be used by other
// components to send metrics.
prometheus.remote_write <span class="hljs-string">"prod"</span> {
  endpoint {
    url = <span class="hljs-string">"https://qryn:3100/api/v1/write"</span>

    basic_auth {
      username = <span class="hljs-string">"admin"</span>

      // Use the password file to authenticate with the production database.
      password = local.file.api_key.content
    }
  }
}

// Find Kubernetes pods <span class="hljs-built_in">where</span> we can collect metrics.
//
// This component exports a <span class="hljs-string">"targets"</span> value, <span class="hljs-built_in">which</span> contains the list of
// discovered pods.
discovery.kubernetes <span class="hljs-string">"pods"</span> {
  role = <span class="hljs-string">"pod"</span>
}

// Collect metrics from Kubernetes pods and send them to prod.
prometheus.scrape <span class="hljs-string">"default"</span> {
  targets    = discovery.kubernetes.pods.targets
  forward_to = [prometheus.remote_write.prod.receiver]
}
</code></pre>
<h3 id="heading-loki-prometheus-forwarder-to-qryn">🔶 Loki + Prometheus forwarder to qryn</h3>
<pre><code class="lang-bash">prometheus.scrape <span class="hljs-string">"metrics_test_local_agent"</span> {
    targets = [{
        __address__ = <span class="hljs-string">"127.0.0.1:12345"</span>,
        cluster     = <span class="hljs-string">"localhost"</span>,
    }]
    forward_to      = [prometheus.remote_write.metrics_test.receiver]
    job_name        = <span class="hljs-string">"local-agent"</span>
    scrape_interval = <span class="hljs-string">"15s"</span>
}

prometheus.remote_write <span class="hljs-string">"metrics_test"</span> {
    endpoint {
        name = <span class="hljs-string">"qryn"</span>
        url  = <span class="hljs-string">"https://qryn:3100/api/prom/push"</span>

        basic_auth {
            username = <span class="hljs-string">"&lt;USERNAME&gt;"</span>
            password = <span class="hljs-string">"&lt;PASSWORD&gt;"</span>
        }

        queue_config { }

        metadata_config { }
    }
}

local.file_match <span class="hljs-string">"logs_varlogs_varlogs"</span> {
    path_targets = [{
        __address__ = <span class="hljs-string">"localhost"</span>,
        __path__    = <span class="hljs-string">"/var/log/*.log"</span>,
        host        = <span class="hljs-string">"mylocalhost"</span>,
        job         = <span class="hljs-string">"varlogs"</span>,
    }]
}

loki.process <span class="hljs-string">"logs_varlogs_varlogs"</span> {
    forward_to = [loki.write.logs_varlogs.receiver]

    stage.match {
        selector = <span class="hljs-string">"{filename=\"/var/log/*.log\"}"</span>

        stage.drop {
            expression = <span class="hljs-string">"^[^0-9]{4}"</span>
        }

        stage.regex {
            expression = <span class="hljs-string">"^(?P&lt;timestamp&gt;\\d{4}/\\d{2}/\\d{2} \\d{2}:\\d{2}:\\d{2}) \\[(?P&lt;level&gt;[[:alpha:]]+)\\] (?:\\d+)\\#(?:\\d+): \\*(?:\\d+) (?P&lt;message&gt;.+)$"</span>
        }

        stage.pack {
            labels           = [<span class="hljs-string">"level"</span>]
            ingest_timestamp = <span class="hljs-literal">false</span>
        }
    }
}

loki.source.file <span class="hljs-string">"logs_varlogs_varlogs"</span> {
    targets    = local.file_match.logs_varlogs_varlogs.targets
    forward_to = [loki.process.logs_varlogs_varlogs.receiver]

    file_watch {
        min_poll_frequency = <span class="hljs-string">"1s"</span>
        max_poll_frequency = <span class="hljs-string">"5s"</span>
    }
}

loki.write <span class="hljs-string">"logs_varlogs"</span> {
    endpoint {
        url = <span class="hljs-string">"https://qryn:3100/loki/api/v1/push"</span>
    }
    external_labels = {}
}
</code></pre>
<p>There's so much more you can do with alloy and qryn combined integration. Let us know your ideas, feedback and experience through our <a target="_blank" href="https://github.com/metrico/qryn">Github repository</a>.</p>
<h2 id="heading-get-polyglot">🔶 Get Polyglot</h2>
<p><a target="_blank" href="https://github.com/metrico/qryn">qryn</a> is the observability system you've been waiting for - its <em>free and opensource</em></p>
<p><em>Logs, Metrics, Traces, Continuous Profiling. All of the power, none of the stress!</em></p>
<p><a target="_blank" href="https://qryn.metrico.in/#/"><img src="https://github.com/metrico/qryn-docs/assets/1423657/a5164f98-d3ed-4638-afe5-c87d252c74af" alt class="image--center mx-auto" /></a></p>
]]></content:encoded></item><item><title><![CDATA[🦗 Odigos + qryn = zero instrumentation]]></title><description><![CDATA[Detect and Fix Production Issues Faster with Odigos & qryn
Odigos is designed to automatically instruments get distributed traces, metrics and logs for any Kubernetes application in minutes, without any code changes.
Odigos detects the programming la...]]></description><link>https://blog.gigapipe.com/odigos-qryn-zero-instrumentation</link><guid isPermaLink="true">https://blog.gigapipe.com/odigos-qryn-zero-instrumentation</guid><category><![CDATA[odigos]]></category><category><![CDATA[eBPF]]></category><category><![CDATA[k8s]]></category><category><![CDATA[observability]]></category><category><![CDATA[OpenTelemetry]]></category><dc:creator><![CDATA[Lorenzo Mangani]]></dc:creator><pubDate>Tue, 27 Feb 2024 23:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1668372625515/E47aZoupa.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668371603629/YNQ9pgxne.png" alt /></p>
<h4 id="heading-detect-and-fix-production-issues-faster-with-odigos-amp-qryn">Detect and Fix Production Issues Faster with Odigos &amp; qryn</h4>
<p><a target="_blank" href="https://github.com/keyval-dev/odigos">Odigos</a> is designed to automatically instruments get distributed traces, metrics and logs for any Kubernetes application in minutes, without any code changes.</p>
<p><strong>Odigos</strong> detects the programming language of your applications and applies automatic instrumentation using well-known, battle-tested open source observability technologies such as <strong>OpenTelemetry</strong> and <strong>eBPF</strong>.</p>
<h2 id="heading-tutorial">Tutorial</h2>
<p>In this tutorial we are going to use <strong>Odigos</strong> for getting automatic observability of a microservices application written in <em>Go, Java, Python, .NET and Node</em>.</p>
<p>Odigos v0.1.36+ natively supports <strong>qryn</strong> as destination for <em>traces, logs and metrics</em>.</p>
<p>📚 This guide is adapted from the <a target="_blank" href="https://odigos.io/docs/getting-started/#choosing-where-to-send-the-data">odigos documentation examples</a></p>
<h3 id="heading-prerequisites">Prerequisites</h3>
<p>To follow the guide, you need the following:</p>
<ul>
<li><p>A <strong>Kubernetes</strong> cluster.</p>
</li>
<li><p><strong>Helm CLI</strong> for installing helm charts.</p>
</li>
<li><p>A <strong>qryn</strong> or <strong>qryn.cloud</strong> deployment.</p>
</li>
</ul>
<h3 id="heading-creating-the-kubernetes-cluster">Creating the Kubernetes cluster</h3>
<p>Create a new local Kubernetes cluster, by running the following command:</p>
<pre><code class="lang-plaintext">kind create cluster
</code></pre>
<h3 id="heading-deploying-the-target-application">Deploying the target application</h3>
<p>For this tutorial, we are going to install a fork of <a target="_blank" href="https://github.com/keyval-dev/microservices-demo">microservices-demo</a>. We use a modified version without any instrumentation code to demonstrate how Odigos automatically collects observability data from the application.</p>
<p>Deploy the demo application using the following command:</p>
<pre><code class="lang-plaintext">kubectl apply -f https://raw.githubusercontent.com/keyval-dev/microservices-demo/master/release/kubernetes-manifests.yaml
</code></pre>
<p>Before proceeding, make sure that <em>all the application pods are running.</em></p>
<h2 id="heading-installing-odigos">Installing Odigos</h2>
<p>The easiest way to install <strong>Odigos</strong> is to use the official helm chart:</p>
<pre><code class="lang-plaintext">helm repo add odigos https://keyval-dev.github.io/odigos-charts/
helm install my-odigos odigos/odigos --namespace odigos-system --create-namespace
</code></pre>
<p>After all the pods in the <code>odigos-system</code> namespace are running, open the Odigos UI by running the following command and navigate to [http://localhost:3000]</p>
<pre><code class="lang-plaintext">kubectl port-forward svc/odigos-ui 3000:3000 -n odigos-system
</code></pre>
<h3 id="heading-choosing-where-to-send-the-data">Choosing where to send the data</h3>
<p>You should now see the following page:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668370960702/n3KKkeh-_.png" alt="image.png" /></p>
<p>After <strong>Odigos</strong> detected all the applications in the cluster, choose the <code>opt out</code> option for application instrumentation. <code>opt in</code> mode is recommended when you want to have greater control over which applications are instrumented.</p>
<p>On the next page, select <code>qryn</code> as the destination for the data:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1670182473440/8fBxmI7Yi.png" alt /></p>
<p>Fill in the following information using your relevant <strong>qryn</strong> details:</p>
<ul>
<li><p><strong>Tempo URL</strong>: <code>https://qryn.host/tempo/api/push</code></p>
</li>
<li><p><strong>Prometheus URL</strong>: <code>https://qryn.host/api/prom/remote/write</code></p>
</li>
<li><p><strong>Loki URL</strong>: <code>https://qryn.host/loki/api/v1/push</code></p>
</li>
</ul>
<h3 id="heading-generating-data">Generating data</h3>
<p>That’s it! Odigos will automatically do the following:</p>
<ul>
<li><p>Instrument all the applications in the cluster:</p>
</li>
<li><p>Runtime languages will be instrumented using <strong>OpenTelemetry</strong>.</p>
</li>
<li><p>Compiled languages will be instrumented using <strong>eBPF</strong>.</p>
</li>
<li><p>Deploy and configure a collector to send the data to <strong>qryn</strong>.</p>
</li>
</ul>
<p>Now all that is left is to generate some traffic in the application.</p>
<p>Execute the following command to port forward into the application UI:</p>
<pre><code class="lang-plaintext">kubectl port-forward svc/frontend 1234:80 -n default
</code></pre>
<p>Navigate to [http://localhost:1234] and perform fake some purchases.</p>
<h3 id="heading-exploring-the-collected-data">Exploring the collected data</h3>
<p>Within minutes, you should see distributed traces appear in <strong>qryn</strong>. You now have all the data needed to understand how your application is behaving, without having to do any additional work. Using this configuration any new application deployed to this Kubernetes cluster will automatically be instrumented and sent to <strong>qryn</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668430911556/xNjwxVD-R.png" alt="image.png" /></p>
<h4 id="heading-cleanup">Cleanup</h4>
<p>Delete the Kubernetes cluster by running the following command:</p>
<pre><code class="lang-plaintext">kind delete cluster
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p><strong>Odigos</strong> is pretty amazing at generating distributed traces, metrics and logs for any K8s application in minutes, and <strong>qryn</strong> supports it as transparently as it should acting as a polyglot backend for all datatypes, without wasting your time with complexity.</p>
<p>Kudos to team <a target="_blank" href="https://keyval.dev">keyval</a> for this fantastic project. Check out <a target="_blank" href="https://odigos.io/">Odigos Cloud,</a> too!</p>
<p><em>Have fun instrumenting your real Applications, and please share your comments!</em></p>
<h3 id="heading-qryn-cloud">🌥 qryn cloud</h3>
<p>Try this example and many more from the comfort of your screen using <a target="_blank" href="https://qryn.cloud"><strong>qryn cloud</strong></a></p>
<p><a target="_blank" href="https://qryn.dev"><img src="https://github.com/metrico/qryn-docs/assets/1423657/a5164f98-d3ed-4638-afe5-c87d252c74af" alt /></a></p>
]]></content:encoded></item><item><title><![CDATA[🦟 Fly.io Observability with qryn]]></title><description><![CDATA[Fly is a platform for running full stack apps and databases close to your users. We’ve been hammering on this thing since 2017, and we think it’s pretty great.

Lots of smart people love Fly.io and run their apps on this great platform!
Each Fly Apps...]]></description><link>https://blog.gigapipe.com/flyio-observability-with-qryn</link><guid isPermaLink="true">https://blog.gigapipe.com/flyio-observability-with-qryn</guid><category><![CDATA[fly.io]]></category><category><![CDATA[qryn]]></category><category><![CDATA[observability]]></category><category><![CDATA[Logs]]></category><category><![CDATA[metrics]]></category><category><![CDATA[distributed tracing]]></category><category><![CDATA[Grafana]]></category><dc:creator><![CDATA[Lorenzo Mangani]]></dc:creator><pubDate>Sat, 20 Jan 2024 11:18:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1705748611668/90c0dade-e148-4e69-acd6-74cc70fb682f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://fly.io"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705710706485/97499114-600f-4912-a883-69774267b4b4.png" alt /></a></p>
<blockquote>
<p>Fly is a platform for running full stack apps and databases close to your users. We’ve been hammering on this thing since 2017, and we think it’s pretty great.</p>
</blockquote>
<p><em>Lots of smart people love Fly.io and run their apps on this great platform!</em></p>
<p>Each <strong>Fly Apps</strong> will produce logs, helpful if not essential for a variety of use cases - <em>debugging, tracking, collating, correlating, coalescing, and condensing</em> the happenings of your running code into useful bits of human-parsable information.</p>
<p>But since <a target="_blank" href="http://Fly.io">Fly.io</a> <em>doesn’t keep your logs around forever</em> to make the best of them we can store and explore data using services such as <a target="_blank" href="https://qryn.dev">qryn</a> and <a target="_blank" href="https://qryn.cloud">qryn.cloud</a>, without loosing any of the <strong>Grafana</strong> compatibility and Dashboards Fly.io offers by default.</p>
<p><em><mark>How can we do that</mark>?</em> Luckily for us fly.io is always super elegant and very generously <a target="_blank" href="https://fly.io/blog/shipping-logs/">routes all Firecracker VM logs through NATS</a>, ready to be picked up and processed.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">👊 Thanks to our good friend <a target="_blank" href="https://twitter.com/dan_the_goodman?lang=en">Dan The Goodman</a> from <a target="_blank" href="https://www.tangia.co/">Tangia</a> for suggesting this smart approach and for inspiring this guide! Checkout his projects!</div>
</div>

<h3 id="heading-fly-higher-with-qryn">🎈 Fly higher with qryn</h3>
<p>To get logs all you need is an app that acts as a NATS client, reads the logs, and ships them somewhere. <strong>Vector</strong> can do just that and since it supports <strong>qryn</strong> natively through the <strong>Loki</strong> and <strong>Prometheus</strong> sinks, <em>it works out of the box with our example.</em></p>
<h2 id="heading-using-the-log-shipper"><strong>Using the Log Shipper</strong></h2>
<p>For our example we'll use use the <a target="_blank" href="https://github.com/superfly/fly-log-shipper">fly-log-shipper example</a>, extended for <strong>qryn</strong>.</p>
<p>The fly <strong>NATS</strong> log stream is scoped to your user organization, which means the Fly Log Shipper collects logs from <em>all</em> your applications at once. <em>Very practical.</em></p>
<p>Here's a quick example using a custom <strong>qryn</strong> sink for shipping <strong>logs</strong> and <strong>metrics</strong>:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Make a directory for qryn log shipper</span>
mkdir logshippper
<span class="hljs-built_in">cd</span> logshippper

<span class="hljs-comment"># I chose not to deploy yet</span>
fly launch --image qxip/fly-log-shipper:latest

<span class="hljs-comment"># Set secrets to enable qryn integration</span>
fly secrets <span class="hljs-built_in">set</span> ORG=personal
fly secrets <span class="hljs-built_in">set</span> ACCESS_TOKEN=$(fly auth token)
fly secrets <span class="hljs-built_in">set</span> QRYN_URL=&lt;qryn API URL&gt;
fly secrets <span class="hljs-built_in">set</span> QRYN_USERNAME=&lt;qryn API user&gt;
fly secrets <span class="hljs-built_in">set</span> QRYN_PASSWORD=&lt;qryn API token&gt;
</code></pre>
<p>Before launching your application, you should edit the generated <code>fly.toml</code> file and delete the entire <code>[[services]]</code> section. Replace it with this:</p>
<pre><code class="lang-ini"><span class="hljs-section">[[services]]</span>
  <span class="hljs-attr">http_checks</span> = []
  <span class="hljs-attr">internal_port</span> = <span class="hljs-number">8686</span>
</code></pre>
<p>Once ready deploy the <strong>qryn log shipper</strong> application to your stack:</p>
<pre><code class="lang-bash">fly deploy
</code></pre>
<p>You’ll soon start to see <strong>logs</strong> appear from all of your apps:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705672682151/3f34fbda-4250-4610-8fdb-dde5fcb3336a.png" alt /></p>
<blockquote>
<p><em>NOTE: shipping logs to qryn does</em> <strong><em>not</em></strong> <em>interrupt the native logging feature!</em></p>
</blockquote>
<p><strong><em><mark>What's happening under the hood?</mark></em></strong></p>
<ul>
<li><p><strong>Vector</strong> receives all the organization logs through <strong>NATS</strong></p>
</li>
<li><p><strong>NATS</strong> sourced logs are parsed and labeled for the Loki sink</p>
</li>
<li><p><strong>Vector</strong> ships parsed logs to a Loki sink pointed at <strong>qryn</strong> or <strong>qryn.cloud</strong></p>
</li>
</ul>
<h3 id="heading-fly-metrics">Fly Metrics</h3>
<p>Fly.io produces granular metrics for all running applications using an internal shared Prometheus and Grafana service. Those seems to be long-retention but you might still want to pull them into your own stack for further processing and analysis.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705749285513/1f25ac41-4625-47b8-a150-4678f7ea0ed3.png" alt class="image--center mx-auto" /></p>
<p>To scrape those <strong>metrics into our custom solution</strong> we can use an extension to the existing Vector configuration to scrape all our organization timeseries using the same Fly.io access token used for our NATS connections, query matching all apps.</p>
<pre><code class="lang-ini"><span class="hljs-section">[sources.flyio_metrics]</span>
  <span class="hljs-attr">type</span> = <span class="hljs-string">"prometheus_scrape"</span>
  <span class="hljs-attr">endpoints</span> = [ <span class="hljs-string">"https://api.fly.io/prometheus/personal/federate"</span> ]
  <span class="hljs-attr">auth.strategy</span> = <span class="hljs-string">'bearer'</span>
  <span class="hljs-attr">auth.token</span> = <span class="hljs-string">"${ACCESS_TOKEN?}"</span>
  <span class="hljs-attr">query.match</span> = [<span class="hljs-string">'{app=~".+"}'</span>]
</code></pre>
<p>The resulting metrics are shipped to <strong>qryn</strong> using a standard <strong>remote_write</strong> API. Easy!</p>
<p><em>To reproduce the full experience, export &amp; recycle Fly's Grafana dashboards.</em></p>
<h3 id="heading-olap-in-one">OLAP-in-One</h3>
<p><strong>That's it!</strong> You're now ready to query your logs using <strong>Loki/LogQL</strong> and your metrics using <strong>Prometheus/PromQL</strong> and ready to receive Traces and Continuous Profiling events from within your App code using <strong>Tempo</strong> and <strong>Pyroscope</strong> compatibility.</p>
<p><em>Using the same process you can ship logs, metrics or traces from any stack.</em></p>
<h3 id="heading-get-polyglot">Get Polyglot</h3>
<p>Tired of unnecessary complexity? Join our <strong>polyglot observability</strong> journey today and regain control of your data with <a target="_blank" href="https://qryn.dev">qryn oss</a> or <a target="_blank" href="https://qryn.cloud">qryn.cloud</a> for all of your <strong>Logs, Metrics, Traces</strong> and <strong>Profiling</strong> while saving a ton in cost and resources and without forcing your teams to learn new query languages and without having to change your tools.</p>
<p>Which stack would you like to see next? Let us know in the comments.</p>
<p><a target="_blank" href="https://qryn.cloud"><img src="https://github.com/metrico/qryn-docs/assets/1423657/a5164f98-d3ed-4638-afe5-c87d252c74af" alt /></a></p>
]]></content:encoded></item><item><title><![CDATA[🛶 Replacing Pyroscope with qryn + otel]]></title><description><![CDATA[If you work with observability and continuous profiling you surely have already heard of, use and/or have tried Grafana Pyroscope

Grafana Pyroscope is an open source software project for aggregating continuous profiling data. Continuous profiling is...]]></description><link>https://blog.gigapipe.com/pyroscope-qryn</link><guid isPermaLink="true">https://blog.gigapipe.com/pyroscope-qryn</guid><category><![CDATA[pprof]]></category><category><![CDATA[pyroscope]]></category><category><![CDATA[Grafana]]></category><category><![CDATA[observability]]></category><category><![CDATA[OLAP]]></category><category><![CDATA[qryn]]></category><category><![CDATA[continuous profiling]]></category><dc:creator><![CDATA[Lorenzo Mangani]]></dc:creator><pubDate>Wed, 17 Jan 2024 10:16:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1705486300985/26dc0a86-3401-4aa6-b1c1-dbb2749c710f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://qryn.cloud"><img src="https://grafana.com/docs/pyroscope/latest/logo.png" alt class="image--center mx-auto" /></a></p>
<p>If you work with observability and continuous profiling you surely have already heard of, use and/or have tried <strong>Grafana Pyroscope</strong></p>
<blockquote>
<p><strong><em>Grafana Pyroscope</em></strong> <em>is an open source software project for aggregating continuous profiling data. Continuous profiling is an observability signal that allows you to understand your workload’s resources (CPU, memory, etc.) usage down to the line number.</em></p>
</blockquote>
<p><strong>Grafana</strong> acquired <strong>Pyroscope</strong> and merged it with its <strong>Phlare</strong> continuous profiling database back in 2023. The smooth integration of <strong>Grafana Pyroscope</strong> with <strong>Grafana</strong> is one of its the main solution benefits allowing easy correlation of <strong>continuous profiling</strong> data with other observability signals, such as <strong>metrics, logs,</strong> and <strong>traces</strong>.</p>
<p><em>This is fantastic</em> until we get reminded Grafana products force their users to maintain completely different backends and datastores for their <em>"correlated"</em> products, pushing lots of complexity for opensource integrations and tedious management. So if you need logs, metrics, traces and profiling - <em>those are 4 parallel deployments.</em></p>
<p>This is where <a target="_blank" href="https://qryn.dev"><strong>qryn</strong></a> kicks in! <mark>One powerful stack, </mark> <em><mark>fluent in all languages.</mark></em></p>
<h3 id="heading-another-pyroscope">Another Pyroscope</h3>
<p>Our observability stack <a target="_blank" href="https://qryn.dev"><strong>qryn</strong></a> already offers native compatibility with <strong>Grafana Loki, Mimir/Prometheus</strong> and <strong>Tempo</strong> as part of its Polyglot API on top of OLAP databases and we're happy to announce <strong>Pyroscope</strong> API support has joined the family!</p>
<p>Piece by piece, we integrated all the parts and protocols to make the integration work in qryn, with the generous cooperation of community resident <strong>Tomer Shafir</strong> ⭐</p>
<p>Profiling data now lives within the same OLAP database as logs, metrics and traces, providing integrators with full access and control for predictable cost/performance.</p>
<p><em>That's the polyglot</em> approach of <strong>qryn</strong>. <a target="_blank" href="https://qryn.dev"><em><mark>More features. Less services. One datastore.</mark></em></a></p>
<h3 id="heading-get-started">Get Started</h3>
<p>Users and integrators of <a target="_blank" href="https://qryn.dev"><strong>qryn 3.0</strong></a> can already ingest <strong>continuous profiling data</strong> in their favourite OLAP database by using the <a target="_blank" href="https://github.com/metrico/otel-collector">qryn <strong>opentelemetry</strong> collector</a> integration for java clients <em>(soon to be extended to support a broader scope of pprof clients)</em> and query profiling data in <strong>Grafana</strong> through the <strong>Pyroscope Datasource</strong> using <em>qryn's</em> APIs.</p>
<p><a target="_blank" href="https://qryn.dev"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705411909102/5085dca1-8bb8-43c0-bed2-a78dcad46e89.png" alt class="image--center mx-auto" /></a></p>
<p>The search and filtering experience are fully compatible the <em>original Pyroscope API</em>, and can be instantly used without any additional knowledge, plugins or hacks</p>
<p><a target="_blank" href="https://qryn.dev"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705485625334/ed6ce7a3-2935-4496-9b5d-f8dc2e20c5ac.gif" alt class="image--center mx-auto" /></a></p>
<h3 id="heading-update-082024">UPDATE 08/2024</h3>
<p><strong>qryn</strong> is fully compatible with the <strong>Pyroscope Explore App</strong> in Grafana!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1725749132907/487fd2a4-32c5-4a68-9110-6c527b39e413.gif" alt class="image--center mx-auto" /></p>
<p>With the opensource front being already on the way, the integration with <a target="_blank" href="https://qryn.cloud">qryn.cloud</a> is up next with focus on total integration with all available <a target="_blank" href="https://gigapipe.com">gigapipe</a> products, as well as any external customer resource through collectors and agents. <em>Watch this space!</em></p>
<p><strong><em>Updated Workflow</em></strong></p>
<p>Here's the upcoming qryn workflow updated with Profiling Features. <em>To the future!</em></p>
<p><a target="_blank" href="https://qryn.dev"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705494697945/e3711142-9aac-493f-814e-4fc00b32e8b8.gif" alt class="image--center mx-auto" /></a></p>
]]></content:encoded></item><item><title><![CDATA[🔎 React App observability with otel + qryn]]></title><description><![CDATA[React - also referred to as React.js or ReactJS - is a JavaScript library for creating user interfaces through UI components. This React library is open-source and free to use, maintained by Meta and groups of independent developers and businesses. A...]]></description><link>https://blog.gigapipe.com/react-app-observability-with-otel-qryn</link><guid isPermaLink="true">https://blog.gigapipe.com/react-app-observability-with-otel-qryn</guid><category><![CDATA[OpenTelemetry]]></category><category><![CDATA[React]]></category><category><![CDATA[observability]]></category><category><![CDATA[monitoring]]></category><category><![CDATA[qryn]]></category><category><![CDATA[monitoring tool]]></category><category><![CDATA[library]]></category><dc:creator><![CDATA[Lorenzo Mangani]]></dc:creator><pubDate>Mon, 04 Dec 2023 17:52:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1701705463014/b9456a92-674f-4d03-a580-972882664f82.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>React</strong> - also referred to as <em>React.js or ReactJS</em> - is a <strong>JavaScript library</strong> for creating user interfaces through UI components. This React library is open-source and free to use, maintained by Meta and groups of independent developers and businesses. As per any webapplication running remotely, <em>troubleshooting React can be challenging.</em></p>
<p><strong>Opentelemetry</strong> is a library ecosystem that implements instrumentation for common libraries and frameworks, providing automatic instrumentation for components that generate <strong>end-to-end telemetry</strong> data without requiring major code changes.</p>
<p><strong>React</strong> and <strong>Opentelemetry both</strong> provide client side coverage. A trace collector is required to receive and index traces. Our polyglot stack <a target="_blank" href="https://qryn.dev"><strong>qryn</strong></a> is designed to be 100% compatible with Opentelemetry standards and supports ingestion via Otel Collectors or directly through the <a target="_blank" href="https://qryn.metrico.in/#/support?id=otel-collector"><em>built-in OTEL ingestion API</em></a><em>, with no additional middleware.</em></p>
<p><strong><mark>"Webservability"</mark></strong> is what happens when you mix these amazing technologies!</p>
<p><a target="_blank" href="https://qryn.dev"><img src="https://opentelemetry.io/img/social/logo-wordmark-001.png" alt /></a></p>
<h3 id="heading-opentelemetry-react">Opentelemetry + React</h3>
<p><strong>Opentelemetry web</strong> libraries can be used for instrumenting and tracing React applications, enabling developers to identify and resolve performance issues and bugs, track user requests originating from frontend to backend services and back.</p>
<h3 id="heading-requirements"><strong>Requirements</strong></h3>
<p>Let's install the necessary <strong>Opentelemetry</strong> libraries as first step:</p>
<pre><code class="lang-bash">bashCopy codenpm install @opentelemetry/api @opentelemetry/sdk-trace-web @opentelemetry/exporter-trace-otlp-http @opentelemetry/auto-instrumentations-web
</code></pre>
<h3 id="heading-manual-instrumentation">⚛️ <strong>Manual Instrumentation</strong></h3>
<p>After installing the libraries, the next step is to configure our Opentelemetry exporter. Here's a basic setup for <code>@opentelemetry/exporter-trace-otlp-http</code>:</p>
<pre><code class="lang-javascript">javascriptCopy codeimport { OTLPTraceExporter } <span class="hljs-keyword">from</span> <span class="hljs-string">'@opentelemetry/exporter-trace-otlp-http'</span>;
<span class="hljs-keyword">import</span> { WebTracerProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">'@opentelemetry/sdk-trace-web'</span>;
<span class="hljs-keyword">import</span> { SimpleSpanProcessor } <span class="hljs-keyword">from</span> <span class="hljs-string">'@opentelemetry/sdk-trace-base'</span>;
<span class="hljs-keyword">import</span> { registerInstrumentations } <span class="hljs-keyword">from</span> <span class="hljs-string">'@opentelemetry/instrumentation'</span>;
<span class="hljs-comment">// Initialize OTLP Exporter</span>
<span class="hljs-keyword">const</span> otlpExporter = <span class="hljs-keyword">new</span> OTLPTraceExporter({
  <span class="hljs-attr">url</span>: <span class="hljs-string">'https://qryn:3100/v1/traces'</span>
});
<span class="hljs-comment">// Initialize the Tracer Provider</span>
<span class="hljs-keyword">const</span> provider = <span class="hljs-keyword">new</span> WebTracerProvider();
<span class="hljs-comment">// Add OTLP Exporter to the provider</span>
provider.addSpanProcessor(<span class="hljs-keyword">new</span> SimpleSpanProcessor(otlpExporter));
<span class="hljs-comment">// Register the provider</span>
provider.register();
</code></pre>
<p>You can now create <strong>custom spans</strong> in your components to trace specific operations or user interactions by wrapping the spans around your code of interest.</p>
<pre><code class="lang-javascript">javascriptCopy codeimport { trace } <span class="hljs-keyword">from</span> <span class="hljs-string">'@opentelemetry/api'</span>;
<span class="hljs-keyword">const</span> tracer = trace.getTracer(<span class="hljs-string">'your-tracer-name'</span>);
<span class="hljs-keyword">const</span> MyComponent = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> span = tracer.startSpan(<span class="hljs-string">'MyComponentRender'</span>);
  <span class="hljs-comment">/* 
     Perform some operations 
  */</span>
  span.end();
  <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>My React Component<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>;
};
</code></pre>
<p>The configured OTLP exporter will send the collected traces to the specified OTEL API endpoint - in our case either <a target="_blank" href="https://qryn.dev"><strong>qryn</strong></a> or <a target="_blank" href="https://qryn.cloud"><strong>qryn.cloud</strong></a> (<a target="_blank" href="https://qryn:3100/v1/traces"><code>https://qryn:3100/v1/traces</code></a>)</p>
<h3 id="heading-auto-instrumentation">⚛️ <strong>Auto-Instrumentation</strong></h3>
<p><strong>Auto-instrumentation</strong> in the context of a React application using <strong>Opentelemetry</strong> primarily revolves around <strong>automatically capturing relevant telemetry</strong> data like user interactions, component render times, and API requests. This can be particularly useful as it <em>minimizes the manual instrumentation code</em> you need to write and maintain. Here's an example of how you can set up auto-instrumentation.</p>
<p>To begin, you need to install the <code>@opentelemetry/auto-instrumentations-web</code> package, which provides auto-instrumentation capabilities for web applications.</p>
<pre><code class="lang-bash">npm install @opentelemetry/auto-instrumentations-web
</code></pre>
<p>After installing the package, you can configure your <strong>Opentelemetry</strong> setup to automatically instrument your React application. Here's an example setup:</p>
<pre><code class="lang-javascript">javascriptCopy codeimport { WebTracerProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">'@opentelemetry/sdk-trace-web'</span>;
<span class="hljs-keyword">import</span> { OTLPTraceExporter } <span class="hljs-keyword">from</span> <span class="hljs-string">'@opentelemetry/exporter-trace-otlp-http'</span>;
<span class="hljs-keyword">import</span> { SimpleSpanProcessor } <span class="hljs-keyword">from</span> <span class="hljs-string">'@opentelemetry/sdk-trace-base'</span>;
<span class="hljs-keyword">import</span> { registerInstrumentations } <span class="hljs-keyword">from</span> <span class="hljs-string">'@opentelemetry/instrumentation'</span>;
<span class="hljs-keyword">import</span> { DocumentLoadInstrumentation, UserInteractionInstrumentation } <span class="hljs-keyword">from</span> <span class="hljs-string">'@opentelemetry/auto-instrumentations-web'</span>;

<span class="hljs-comment">// Initialize OTLP Exporter</span>
<span class="hljs-keyword">const</span> otlpExporter = <span class="hljs-keyword">new</span> OTLPTraceExporter({
  <span class="hljs-attr">url</span>: <span class="hljs-string">'https://qryn:3100/v1/traces'</span>
});
<span class="hljs-comment">// Initialize the Tracer Provider</span>
<span class="hljs-keyword">const</span> provider = <span class="hljs-keyword">new</span> WebTracerProvider();
<span class="hljs-comment">// Add OTLP Exporter to the provider</span>
provider.addSpanProcessor(<span class="hljs-keyword">new</span> SimpleSpanProcessor(otlpExporter));
<span class="hljs-comment">// Register the provider</span>
provider.register();
<span class="hljs-comment">// Auto-instrumentations</span>
registerInstrumentations({
  <span class="hljs-attr">instrumentations</span>: [
    <span class="hljs-keyword">new</span> DocumentLoadInstrumentation(),
    <span class="hljs-keyword">new</span> UserInteractionInstrumentation(),
  ],
  <span class="hljs-attr">tracerProvider</span>: provider,
});
</code></pre>
<p>When using <strong>auto-instrumentations</strong> your React application will automatically <em>generate and export traces</em> related to document loads and user interactions, providing valuable insights on your application internals <em>with minimal manual setup.</em></p>
<h3 id="heading-happy-conclusion"><strong>Happy Conclusion</strong></h3>
<p><strong>Opentelemetry</strong> in <strong>React</strong> combined with the polyglot features of <a target="_blank" href="https://qryn.dev"><strong>qryn</strong></a> or <a target="_blank" href="https://qryn.cloud"><strong>qryn.cloud</strong></a> delivers one of the fastest and lightest end-to-end observability pairs ever!</p>
<p><a target="_blank" href="https://qryn.dev"><strong>qryn</strong></a> supports Opentelemetry ingestion natively and through a collectors reducing project requirements and moving parts to a minimum - <em>with maximum results.</em></p>
<p><a target="_blank" href="https://qryn.dev"><em>That's it! Give it a try in our code and share your feedback and suggestions!</em></a></p>
<p><a target="_blank" href="https://qryn.dev"><img src="https://user-images.githubusercontent.com/1423657/218818279-3efff74f-0191-498a-bdc4-f2650c9d3b49.gif" alt class="image--center mx-auto" /></a></p>
]]></content:encoded></item><item><title><![CDATA[📞 WebRTC Observability with qryn]]></title><description><![CDATA[In this interesting medium article, RTC veteran Vittorio Palmisano shows his audience how to simplify the debugging process of WebRTC applications by exporting all RTCPeerConnections metrics generated by Browser sessions out to a Prometheus + PushGat...]]></description><link>https://blog.gigapipe.com/webrtc-observability-with-qryn</link><guid isPermaLink="true">https://blog.gigapipe.com/webrtc-observability-with-qryn</guid><category><![CDATA[WebRTC]]></category><category><![CDATA[debugging]]></category><category><![CDATA[observability]]></category><category><![CDATA[chrome extension]]></category><category><![CDATA[Grafana]]></category><category><![CDATA[#prometheus]]></category><dc:creator><![CDATA[Lorenzo Mangani]]></dc:creator><pubDate>Mon, 04 Dec 2023 01:41:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727377698166/06bfa2ee-3a66-4abe-a1b7-2a7a78d3391b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this <a target="_blank" href="https://medium.com/@vpalmisano/webrtc-debugging-with-prometheus-grafana-254b6ac71063">interesting medium article</a>, RTC veteran <strong>Vittorio Palmisano</strong> shows his audience how to simplify the debugging process of <strong>WebRTC applications</strong> by exporting all <code>RTCPeerConnections</code> metrics generated by Browser sessions out to a Prometheus + PushGateway service using a custom built browser extension.</p>
<p><em>If you have Prometheus and PushGateway up, that's great. If you don't.....</em></p>
<p><mark>This is </mark> <strong><mark>exactly </mark></strong> <mark>the challenge we designed </mark> <strong><mark>qryn </mark></strong> <mark>for - </mark> <em><mark>so you know what's next!</mark></em></p>
<p><a target="_blank" href="https://qryn.dev"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701652298981/e2738a04-3f30-47fb-963a-4f5d20ccb3fc.gif" alt class="image--center mx-auto" /></a></p>
<h3 id="heading-requirements">Requirements</h3>
<p>In order to run this experiment you will need the following:</p>
<ul>
<li><p><a target="_blank" href="https://qryn.dev">qryn</a> or <a target="_blank" href="https://qryn.cloud">qryn.cloud</a> setup to ingest and query metrics</p>
</li>
<li><p><a target="_blank" href="https://qryn.cloud">grafana</a> to display metrics using the <a target="_blank" href="https://gist.github.com/lmangani/8302de53ebfd8df5339643e3a74567ed#file-webrtc_internals-json">included dashboard</a></p>
</li>
<li><p>chrome browser with our <a target="_blank" href="https://github.com/lmangani/webrtc-internals-exporter/releases/download/v0.1.10/webrtc-exporter-v0.1.10.zip">custom extension</a> installed</p>
</li>
</ul>
<h3 id="heading-browser-extension-exporter">Browser Extension Exporter</h3>
<p>For this demo, we'll keep things simple. The original article requires a <strong><s>PushGateway </s></strong> to deliver the Prometheus metrics. The InfluxDB line protocol is a valid alternative candidate for the job, allowing direct delivery to a collector such as <strong>qryn</strong> without extra components, <em>so we modified the extension to add this protocol method.</em></p>
<p>👉 <a target="_blank" href="https://github.com/lmangani/webrtc-internals-exporter/releases/download/v0.1.10/webrtc-exporter-v0.1.10.zip">Download and manually install our extension</a> from our GitHub repository</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701652569478/6eaa9bfa-0435-43e7-9a78-e28c517c1c92.png" alt /></p>
<p>Once installed, you need to visit the <strong>extension options</strong> page, filling the <strong>qryn endpoint</strong> <em>and optional username/password.</em> Other optional settings include:</p>
<ul>
<li><p><strong>Update interval</strong>: it allows to change the metrics collection interval (in seconds).</p>
</li>
<li><p><strong>Enabling gzip compression</strong>: it is not required but recommended in order to reduce the amount of data sent over the network;</p>
</li>
<li><p><strong>Job name</strong>: it allows to customize the <code>job</code> label attached to each metric. Usually, you can query the exported label in Grafana using the <code>exported_job</code> label selector.</p>
</li>
<li><p><strong>Enabled PeerConnection stats</strong>: you can specify here the list metrics types that you want to collect from the <code>getStats</code> output.</p>
</li>
<li><p><strong>Enabled URL origins</strong>: you can see here the list of allowed URLs where the extension will actually collect data.</p>
</li>
</ul>
<p><img src="https://miro.medium.com/v2/resize:fit:1250/1*TVT_Lfk2oNqd1rFShSXKOQ.jpeg" alt /></p>
<p><mark>For a local qryn setup, you will only need the URL parameter: </mark> <strong><mark>http://qryn:3100</mark></strong></p>
<p><strong>Save</strong> your settings and proceed ahead.</p>
<h3 id="heading-talk-to-yourself">Talk to Yourself</h3>
<p><em>Real WebRTC developer and testers always prefer talking to themselves.</em></p>
<p>To join the club, open your Chrome browser and start the <a target="_blank" href="https://janus.conf.meetecho.com/echotest.html"><strong>Janus Echo Test</strong></a></p>
<p><strong>Enable</strong> the <strong>WebRTC Internals Exporter</strong> for the Janus website 👇👇👇</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701652896408/a0542b00-18bd-48e5-84dc-60321d59be40.png" alt class="image--center mx-auto" /></p>
<p>Click <code>start</code> to initiate your WebRTC echo session and begin <strong>producing statistics</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701652073663/7dc34f1e-3a62-4ddc-ad6b-3b0fce15dd6a.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701653132853/7aa2b64a-6f5b-4c05-be5a-6e5b7a365d2d.png" alt /></p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><mark>Keep your echo test going for a few minutes to collect relevant data</mark></div>
</div>

<h3 id="heading-real-time-analytics">📈 Real Time Analytics</h3>
<p>If you followed each step and bad luck didn't get in the way, our metrics should be inserted into qryn and available through the Prometheus API. All of our data points and tags are instantly available on the imported preset <a target="_blank" href="https://gist.github.com/lmangani/8302de53ebfd8df5339643e3a74567ed#file-webrtc_internals-json"><strong>WebRTC Dashboard</strong></a> 👈</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701650874492/0bf3bc36-eea7-4d7d-8333-8d546d9e2480.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p>The extension will convert each numeric value found in the <code>RTCPeerConnections</code> stats using a Gauge metric type, using the remaining string properties as labels. E.g. the <strong>bytesSent</strong> value in the <strong>outbound-rtp</strong> stats will be converted to a metric named <strong>outbound_rtp_bytesSent</strong> using the remaining string properties as labels.</p>
</blockquote>
<p><img src="https://miro.medium.com/v2/resize:fit:1250/1*1VVb2ek_i-eYkHC_0ULzfQ.jpeg" alt /></p>
<p><em><mark>Just imagine the combination of these reports with serverside Janus Events.....</mark></em></p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>That's it! With the power of <strong>opensource</strong> you have added a powerful asset to your real-time communications troubleshooting toolbox, while enjoying the polyglot capabilities of <a target="_blank" href="https://qryn.cloud"><strong>qryn</strong></a> alongside other RTC integrations such as <a target="_blank" href="https://sipcapture.org"><strong>homer</strong></a> and <a target="_blank" href="https://hepic.cloud"><strong>hepic</strong></a>.</p>
<p><em>Let's get polyglot</em> 💛</p>
<p><a target="_blank" href="https://qryn.dev"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701652298981/e2738a04-3f30-47fb-963a-4f5d20ccb3fc.gif" alt class="image--center mx-auto" /></a></p>
]]></content:encoded></item><item><title><![CDATA[Make a Scene... with Grafana 10 + qryn]]></title><description><![CDATA[Grafana Scenes is a new front-end library that enables developers to create dashboard-like experiences — such as querying and transformations, dynamic panel rendering, and time ranges — directly within Grafana application plugins. 
Scenes are collect...]]></description><link>https://blog.gigapipe.com/make-a-scene-with-grafana</link><guid isPermaLink="true">https://blog.gigapipe.com/make-a-scene-with-grafana</guid><category><![CDATA[grafana scenes]]></category><category><![CDATA[Grafana]]></category><category><![CDATA[loki]]></category><category><![CDATA[#prometheus]]></category><category><![CDATA[dashboard]]></category><category><![CDATA[ClickHouse]]></category><dc:creator><![CDATA[Lorenzo Mangani]]></dc:creator><pubDate>Sun, 03 Dec 2023 18:01:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1701701680481/98ed412f-229f-4483-bda9-8db9182b5bd7.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Grafana Scenes</strong> is a new front-end library that enables developers to create dashboard-like experiences — such as querying and transformations, dynamic panel rendering, and time ranges — directly within Grafana application plugins. </p>
<p><a target="_blank" href="https://grafana.com/blog/2023/10/31/create-a-logs-app-plugin-with-grafana-scenes-and-grafana-loki"><strong>Scenes</strong></a> are collections of objects representing different UI components, such as data, time ranges, variables, layout, and visualizations. Let's see how we can use them to build a <strong>Log-centric</strong> Scene using a <strong>qryn</strong> <em>Loki compatible data source.</em></p>
<h3 id="heading-lets-make-a-scene">Let's make a Scene</h3>
<p>Let's begin our journey by exploring the <strong>Apps</strong> item in the left-side menu.</p>
<p>The boilerplate Scenes app comes with three routes: <strong>Page with tabs</strong>, <strong>Page with drilldown</strong>, and <strong>Hello world</strong>. We’re going to focus on the <strong>Home</strong> route located in <code>src/pages/Home.tsx</code> and rendered as the default page of our new app plugin.</p>
<p>The most important objects in a <strong>Grafana Scene</strong> are:</p>
<ul>
<li><p><code>SceneApp</code>: Responsible for top-level pages routing.</p>
</li>
<li><p><code>scene.Component</code>: Used in render functions to render your <code>SceneApp</code>.</p>
</li>
</ul>
<p>Let's begin by customizing the title and subtitle of our new <strong>Log Scenes App</strong></p>
<p><img src="https://grafana.com/media/blog/scenes-loki/logs-scenes-app-title-and-subtitle.png" alt="A screenshot of the title and subtitle for the Logs Scenes App" /></p>
<p>We will include a verification for the existence of a qryn <strong>Loki data source</strong> and make sure to show a notification if it is missing. This will help avoid unforeseen errors and confirm that our setup is prepared for developing our new application.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { DataSourceInstanceSettings } <span class="hljs-keyword">from</span> <span class="hljs-string">'@grafana/data'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">hasLoggingDataSources</span>(<span class="hljs-params">dataSources: Record&lt;string, DataSourceInstanceSettings&gt;</span>) </span>{
     <span class="hljs-keyword">return</span> <span class="hljs-built_in">Object</span>.entries(dataSources).some(<span class="hljs-function">(<span class="hljs-params">[_, ds]</span>) =&gt;</span> ds.type === <span class="hljs-string">'loki'</span>);
}
</code></pre>
<p>Once done, proceed updating your <code>HomePage</code> component.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> HomePage = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> scene = useMemo(<span class="hljs-function">() =&gt;</span> getScene(), []);
    <span class="hljs-keyword">const</span> hasDataSources = useMemo(<span class="hljs-function">() =&gt;</span> hasLoggingDataSources(config.datasources), []);

    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
            {!hasDataSources &amp;&amp; (
            <span class="hljs-tag">&lt;<span class="hljs-name">Alert</span> <span class="hljs-attr">title</span>=<span class="hljs-string">{</span>`<span class="hljs-attr">Missing</span> <span class="hljs-attr">logging</span> <span class="hljs-attr">data</span> <span class="hljs-attr">sources</span>`}&gt;</span>
                    This plugin requires a Loki data source. Please add and configure a Loki data source to your Grafana instance.
            <span class="hljs-tag">&lt;/<span class="hljs-name">Alert</span>&gt;</span>
            )}      
            <span class="hljs-tag">&lt;<span class="hljs-name">scene.Component</span> <span class="hljs-attr">model</span>=<span class="hljs-string">{scene}</span> /&gt;</span>
        <span class="hljs-tag">&lt;/&gt;</span></span>
    );
};
</code></pre>
<p>If your local environment is configured correctly, you should not see any alerts.</p>
<h3 id="heading-scenes-for-a-loki-app"><strong>Scenes for a Loki app</strong></h3>
<p>In this step, our focus will be on the file located in <code>src/pages/Home/scenes.tsx</code>.</p>
<p>In this file, we export the <code>getBasicScene()</code> function which will be used in the SceneAppPage on the Home page. To start, we need to consider the importance of time. Every request we make requires time, and for this application, we need a specific time interval to retrieve the data stored in qryn within that range.</p>
<p>The <code>SceneTimeRange</code> component manages the selection of time for query requests, and it is used in the <code>SceneTimePicker</code> control. In our example, we will create an instance that represents the time between now and one hour before. However, this range can be customized according to your needs. You don't have to worry about the fixed value, as we will allow the user to customize their selection.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { SceneTimeRange } <span class="hljs-keyword">from</span> <span class="hljs-string">'@grafana/scenes'</span>;

<span class="hljs-keyword">const</span> timeRange = <span class="hljs-keyword">new</span> SceneTimeRange({
    <span class="hljs-attr">from</span>: <span class="hljs-string">'now-1h'</span>,
    <span class="hljs-attr">to</span>: <span class="hljs-string">'now'</span>,
  });
</code></pre>
<p>User input is our next goal. To achieve this, we will utilize various controls that generate variables. Each variable will have a specific name and value. The <code>DataSourceVariable</code> enables the user to choose a data source from the ones configured in this Grafana instance. Once a data source is selected, a variable is generated. This variable possesses a designated name and can be utilized in queries and other components.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { DataSourceVariable } <span class="hljs-keyword">from</span> <span class="hljs-string">'@grafana/scenes'</span>;

<span class="hljs-keyword">const</span> dsHandler = <span class="hljs-keyword">new</span> DataSourceVariable({
    <span class="hljs-attr">label</span>: <span class="hljs-string">'Data source'</span>,
    <span class="hljs-attr">name</span>: <span class="hljs-string">'ds'</span>, <span class="hljs-comment">// being $ds the name of the variable holding UID value of the selected data source</span>
    <span class="hljs-attr">pluginId</span>: <span class="hljs-string">'loki'</span>
  });
</code></pre>
<p><code>QueryVariable</code> enables you to showcase the outcomes of a query-based collection of values, like metric names or server names, in a dropdown menu. In this instance, we will request our qryn datasource to return the names of the stream selectors.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { QueryVariable } <span class="hljs-keyword">from</span> <span class="hljs-string">'@grafana/scenes'</span>;

<span class="hljs-keyword">const</span> streamHandler = <span class="hljs-keyword">new</span> QueryVariable({
    <span class="hljs-attr">label</span>: <span class="hljs-string">'Source stream'</span>,
    <span class="hljs-attr">name</span>: <span class="hljs-string">'stream_name'</span>, <span class="hljs-comment">// $stream_name will hold the selected stream</span>
    <span class="hljs-attr">datasource</span>: {
          <span class="hljs-attr">type</span>: <span class="hljs-string">'loki'</span>,
          <span class="hljs-attr">uid</span>: <span class="hljs-string">'$ds'</span> <span class="hljs-comment">// here the value of $ds selected in the DataSourceVariable will be interpolated.</span>
    },
    <span class="hljs-attr">query</span>: <span class="hljs-string">'label_names()'</span>,
  });
</code></pre>
<ul>
<li><code>TextBoxVariable</code> is an input to enter free text. We will use it to select the value of the selected stream.</li>
</ul>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { TextBoxVariable } <span class="hljs-keyword">from</span> <span class="hljs-string">'@grafana/scenes'</span>;
<span class="hljs-keyword">const</span> streamValueHandler = <span class="hljs-keyword">new</span> TextBoxVariable({
    <span class="hljs-attr">label</span>: <span class="hljs-string">'Stream value'</span>,
    <span class="hljs-attr">name</span>: <span class="hljs-string">'stream_value'</span>, <span class="hljs-comment">// $stream_value will hold the user input</span>
  });
</code></pre>
<p>We currently have time and user input stored in our variables. Moving forward, we will utilize both the time and user input to construct queries. This is where the scene component comes into play. The <code>SceneQueryRunner</code> will retrieve data from the qryn Loki data source and deliver the results to a visualization or multiple visualizations.</p>
<p>Each query is represented as a JSON object, containing a reference ID (<code>refid</code>) and an expression that specifies the query to be executed.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { SceneQueryRunner } <span class="hljs-keyword">from</span> <span class="hljs-string">'@grafana/scenes'</span>;

<span class="hljs-keyword">const</span> queryRunner = <span class="hljs-keyword">new</span> SceneQueryRunner({
    <span class="hljs-attr">datasource</span>: {
          <span class="hljs-attr">type</span>: <span class="hljs-string">'loki'</span>,
          <span class="hljs-attr">uid</span>: <span class="hljs-string">'$ds'</span> <span class="hljs-comment">// here the value of $ds selected in the DataSourceVariable will be interpolated.</span>
    },
    <span class="hljs-attr">queries</span>: [
        {
            <span class="hljs-attr">refId</span>: <span class="hljs-string">'A'</span>,
            <span class="hljs-attr">expr</span>: <span class="hljs-string">'your query here'</span>,
        },
    ],
});
</code></pre>
<h3 id="heading-visualizing-data">Visualizing Data</h3>
<p>The <code>PanelBuilders</code> API provides support for building visualization objects for the supported visualization types, such as <code>Stat</code>, <code>TimeSeries</code>, and <code>Logs</code>.</p>
<h4 id="heading-1-stat-panel"><strong>1. Stat panel</strong></h4>
<p>We will begin by creating a stat visualization. Stats display a single prominent value along with an optional graph sparkline. You have the ability to customize the background or value color using thresholds or overrides. To achieve this, we will utilize a <code>QueryRunner</code> and a <code>PanelBuilder</code>.</p>
<p>To gather the necessary data, we will employ a qryn metric query to visualize the rate at which these logs occur within the specified time frame.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { SceneQueryRunner, PanelBuilders } <span class="hljs-keyword">from</span> <span class="hljs-string">'@grafana/scenes'</span>;
<span class="hljs-keyword">import</span> { BigValueGraphMode } <span class="hljs-keyword">from</span> <span class="hljs-string">'@grafana/schema'</span>;

<span class="hljs-keyword">const</span> statQueryRunner = <span class="hljs-keyword">new</span> SceneQueryRunner({
    <span class="hljs-attr">datasource</span>: {
        <span class="hljs-attr">type</span>: <span class="hljs-string">'loki'</span>,
        <span class="hljs-attr">uid</span>: <span class="hljs-string">'$ds'</span>
    },
    <span class="hljs-attr">queries</span>: [
        {
            <span class="hljs-attr">refId</span>: <span class="hljs-string">'A'</span>,
            <span class="hljs-attr">expr</span>: <span class="hljs-string">'sum(rate({$stream_name="$stream_value"} [$__auto]))'</span>,
        },
    ],
});

<span class="hljs-keyword">const</span> statPanel = PanelBuilders.stat()
    .setTitle(<span class="hljs-string">'Logs rate / second'</span>)
    .setData(statQueryRunner)
    .setOption(<span class="hljs-string">'graphMode'</span>, BigValueGraphMode.None)
    .setOption(<span class="hljs-string">'reduceOptions'</span>, {
         <span class="hljs-attr">values</span>: <span class="hljs-literal">false</span>,
         <span class="hljs-attr">calcs</span>: [<span class="hljs-string">'mean'</span>],
         <span class="hljs-attr">fields</span>: <span class="hljs-string">''</span>,
    });
</code></pre>
<p>Like we mentioned earlier, variables are going to be replaced with user input:</p>
<p><code>sum(rate({$stream_name="$stream_value"} [$__interval]))</code></p>
<p>Here's our Panel's logic explained as a sequence: </p>
<ul>
<li><p>Request a <code>Stat</code> from <code>PanelBuilder</code>.</p>
</li>
<li><p>Provide a title.</p>
</li>
<li><p>Tell it where to get the data.</p>
</li>
<li><p>Tell it that we don’t want to see the sparklines, just the big number.</p>
</li>
<li><p>Provide some customizations around how to treat the data. </p>
</li>
</ul>
<p>In order to display a single numerical value on the stat panel, we need to customize it to calculate the mean of the provided values. As metric queries return time series, we can use this customization to see the average of all values in this particular example.</p>
<h4 id="heading-2-time-series"><strong>2. Time series</strong></h4>
<p>The second visualization we’re going to use is a <code>TimeSeries</code> panel, because we want to see how data changes over a period of time.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { SceneQueryRunner, PanelBuilders } <span class="hljs-keyword">from</span> <span class="hljs-string">'@grafana/scenes'</span>;

<span class="hljs-keyword">const</span> timeSeriesQueryRunner = <span class="hljs-keyword">new</span> SceneQueryRunner({
    <span class="hljs-attr">datasource</span>: {
        <span class="hljs-attr">type</span>: <span class="hljs-string">'loki'</span>,
        <span class="hljs-attr">uid</span>: <span class="hljs-string">'$ds'</span>,
    },
    <span class="hljs-attr">queries</span>: [
        {
            <span class="hljs-attr">refId</span>: <span class="hljs-string">'B'</span>,
            <span class="hljs-attr">expr</span>: <span class="hljs-string">'count_over_time({$stream_name="$stream_value"} [$__auto])'</span>,
        },
    ],
});

  <span class="hljs-keyword">const</span> timeSeriesPanel = PanelBuilders
    .timeseries()
    .setTitle(<span class="hljs-string">'Logs over time'</span>)
    .setData(timeSeriesQueryRunner);
</code></pre>
<p>This one is simpler, and works out of the box without customizations.</p>
<h4 id="heading-3-logs-panel"><strong>3. Logs panel</strong></h4>
<p>For our logs panel we will use a qryn <a target="_blank" href="https://grafana.com/docs/loki/latest/query/log_queries/?pg=blog&amp;plcmt=body-txt&amp;src=li&amp;mdm=social&amp;camp=blog">log query</a> + a log visualization widget.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { SceneQueryRunner, PanelBuilders } <span class="hljs-keyword">from</span> <span class="hljs-string">'@grafana/scenes'</span>;

<span class="hljs-keyword">const</span> logsQueryRunner = <span class="hljs-keyword">new</span> SceneQueryRunner({
    <span class="hljs-attr">datasource</span>: {
        <span class="hljs-attr">type</span>: <span class="hljs-string">'loki'</span>,
        <span class="hljs-attr">uid</span>: <span class="hljs-string">'$ds'</span>,
    },
    <span class="hljs-attr">queries</span>: [
        {
            <span class="hljs-attr">refId</span>: <span class="hljs-string">'A'</span>,
            <span class="hljs-attr">expr</span>: <span class="hljs-string">'{$stream_name="$stream_value"}'</span>,
            <span class="hljs-attr">maxLines</span>: <span class="hljs-number">20</span>, <span class="hljs-comment">// Use up to 5000</span>
        },
    ],
});

  <span class="hljs-keyword">const</span> logsPanel = PanelBuilders.logs()
    .setTitle(<span class="hljs-string">'Logs'</span>)
    .setData(logsQueryRunner);
</code></pre>
<h3 id="heading-arrange-the-scenes"><strong>Arrange the scenes</strong></h3>
<p>To organize the scenes, we will follow these steps:</p>
<p>First, we have gathered all the necessary building blocks for our application. Now, in the final stage, we need to pass these objects to the Scenes library and determine how they should be organized in the UI. To achieve this, we will utilize a grid layout.</p>
<p>This layout is the default behavior of dashboards in Grafana, and it provides a similar experience for our scenes. Within the grid layout, we will incorporate three SceneGridItem components. In order to pass the variables to our scene, we will encapsulate them within a SceneVariableSet.</p>
<p>Lastly, we will arrange the scene in a way that grants the user access to certain controls. These controls include:</p>
<ul>
<li><p><code>VariableValueSelectors</code> to modify the variable controls.</p>
</li>
<li><p><code>SceneControlsSpacer</code> to add a little bit of air between objects.</p>
</li>
<li><p><code>SceneTimePicker</code> to customize the time selection.</p>
</li>
</ul>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getBasicScene</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-comment">// Everything before goes over here</span>
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> EmbeddedScene({
        <span class="hljs-attr">$timeRange</span>: timeRange,
        <span class="hljs-attr">$variables</span>: <span class="hljs-keyword">new</span> SceneVariableSet({
            <span class="hljs-attr">variables</span>: [dsHandler, streamHandler, streamValueHandler],
        }),
        <span class="hljs-attr">body</span>: <span class="hljs-keyword">new</span> SceneGridLayout({
            <span class="hljs-attr">children</span>: [
                <span class="hljs-keyword">new</span> SceneGridItem({
                    <span class="hljs-attr">height</span>: <span class="hljs-number">8</span>,
                    <span class="hljs-attr">width</span>: <span class="hljs-number">8</span>,
                    <span class="hljs-attr">x</span>: <span class="hljs-number">0</span>,
                    <span class="hljs-attr">y</span>: <span class="hljs-number">0</span>,
                    <span class="hljs-attr">body</span>: statPanel.build(),
                }),
                <span class="hljs-keyword">new</span> SceneGridItem({
                    <span class="hljs-attr">height</span>: <span class="hljs-number">8</span>,
                    <span class="hljs-attr">width</span>: <span class="hljs-number">16</span>,
                    <span class="hljs-attr">x</span>: <span class="hljs-number">8</span>,
                    <span class="hljs-attr">y</span>: <span class="hljs-number">0</span>,
                    <span class="hljs-attr">body</span>: timeSeriesPanel.build(),
                }),
                <span class="hljs-keyword">new</span> SceneGridItem({
                    <span class="hljs-attr">height</span>: <span class="hljs-number">8</span>,
                    <span class="hljs-attr">width</span>: <span class="hljs-number">24</span>,
                    <span class="hljs-attr">x</span>: <span class="hljs-number">0</span>,
                    <span class="hljs-attr">y</span>: <span class="hljs-number">4</span>,
                    <span class="hljs-attr">body</span>: logsPanel.build(),
                })
            ],
        }),
        <span class="hljs-attr">controls</span>: [
            <span class="hljs-keyword">new</span> VariableValueSelectors({}),
            <span class="hljs-keyword">new</span> SceneControlsSpacer(),
            <span class="hljs-keyword">new</span> SceneTimePicker({ <span class="hljs-attr">isOnCanvas</span>: <span class="hljs-literal">true</span> }),
        ],
    });
}
</code></pre>
<p>If you followed our tutorial, the results should look similar to the following example:</p>
<p><img src="https://grafana.com/media/blog/scenes-loki/logs-scenes-app-screenshot.png" alt="A screenshot of the Logs Scenes App" /></p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">don’t forget to enter a value for the stream value input for the qryn source</div>
</div>

<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Now we know how to develop a Grafana plugin and how to use the Scenes library to constructing interactive interfaces with visualizations using <strong><em>qryn's</em></strong> <em>Loki and LogQL compatible APIs.</em> This experiment further confirms our vision: being <strong>polyglot</strong> rocks!</p>
<p>That's right! Since <strong>qryn</strong> is a <em>drop-in compatible LGTM alternative</em>, any guide or tutorial designed and created for <strong>Grafana Loki</strong>, <strong>Mimir or Tempo</strong> will work out of the box when used with <strong>qryn</strong> and <a target="_blank" href="https://qryn.cloud"><strong>qryn.cloud</strong></a> using our custom made APIs built on top of fast and scalable OLAP databases such as <em>ClickHouse, DuckDB and InfluxDB.</em></p>
<p><em>Ready to try?</em> Deploy <a target="_blank" href="https://qryn.dev">qryn</a> OSS locally or try our <a target="_blank" href="https://qryn.cloud">qryn.cloud</a> managed service 👇</p>
<p><a target="_blank" href="https://qryn.dev"><img src="https://user-images.githubusercontent.com/1423657/218818279-3efff74f-0191-498a-bdc4-f2650c9d3b49.gif" alt class="image--center mx-auto" /></a></p>
]]></content:encoded></item><item><title><![CDATA[LLMs Observability with Traceloop + qryn]]></title><description><![CDATA[OpenLLMetry is a set of extensions built on top of OpenTelemetry that gives you complete observability over your LLM application with minimal complexity.
Because it uses OpenTelemetry under the hood it can be connected to existing observability solut...]]></description><link>https://blog.gigapipe.com/llms-observability-with-traceloop-qryn</link><guid isPermaLink="true">https://blog.gigapipe.com/llms-observability-with-traceloop-qryn</guid><category><![CDATA[llm]]></category><category><![CDATA[openai]]></category><category><![CDATA[LLaMa]]></category><category><![CDATA[observability]]></category><category><![CDATA[monitoring]]></category><category><![CDATA[qryn]]></category><category><![CDATA[gigapipe]]></category><category><![CDATA[ClickHouse]]></category><dc:creator><![CDATA[Lorenzo Mangani]]></dc:creator><pubDate>Thu, 23 Nov 2023 13:58:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1700747676972/90598282-0d4d-4310-96ee-2ceb3919342b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://www.traceloop.com/openllmetry"><img src="https://assets-global.website-files.com/643c87010b62a2009a1f57bf/655cc4769701a8661bb0fe69_illustration-p-1600.png" alt class="image--center mx-auto" /></a></p>
<p><a target="_blank" href="https://www.traceloop.com/openllmetry">OpenLLMetry</a> is a set of extensions built on top of <a target="_blank" href="https://opentelemetry.io/">OpenTelemetry</a> that gives you complete observability over your LLM application with minimal complexity.</p>
<p>Because it uses OpenTelemetry under the hood it can be connected to existing observability solutions such as our <strong>polyglot stack</strong><a target="_blank" href="https://qryn.dev">qryn</a> and <a target="_blank" href="https://qryn.cloud">qryn.cloud</a> ⭐⭐⭐</p>
<h2 id="heading-step-1-traceloop-sdk-setup">Step 1: Traceloop SDK Setup</h2>
<p><a target="_blank" href="https://www.traceloop.com/openllmetry">OpenLLMetry</a> lets you easily trace prompts and embedding calls of <strong>OpenAI</strong> and can provide a complete view of your OpenAI application using traces and spans.</p>
<p>To get started, Install the <a target="_blank" href="https://pypi.org/project/traceloop-sdk/">Traceloop SDK</a> and initialize it within your code.</p>
<h3 id="heading-openai-example">🧠 OpenAI Example</h3>
<p>Automatically log all calls to OpenAI, with prompts and completions</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> openai
<span class="hljs-keyword">from</span> traceloop.sdk <span class="hljs-keyword">import</span> Traceloop
<span class="hljs-keyword">from</span> traceloop.sdk.decorators <span class="hljs-keyword">import</span> workflow

Traceloop.init(app_name=<span class="hljs-string">"joke_generation_service"</span>)

<span class="hljs-meta">@workflow(name="joke_creation")</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_joke</span>():</span>
    completion = openai.ChatCompletion.create(
        model=<span class="hljs-string">"gpt-3.5-turbo"</span>,
        messages=[{<span class="hljs-string">"role"</span>: <span class="hljs-string">"user"</span>, <span class="hljs-string">"content"</span>: <span class="hljs-string">"Tell me a joke about opentelemetry"</span>}],
    )

    <span class="hljs-keyword">return</span> completion.choices[<span class="hljs-number">0</span>].message.content
</code></pre>
<h3 id="heading-llama-example">🦙 LLAMA Example</h3>
<p>Automatically log all calls to LLAMA models, with prompts and completions</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> chromadb
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">import</span> openai

<span class="hljs-keyword">from</span> llama_index <span class="hljs-keyword">import</span> VectorStoreIndex, SimpleDirectoryReader, ServiceContext
<span class="hljs-keyword">from</span> llama_index.vector_stores <span class="hljs-keyword">import</span> ChromaVectorStore
<span class="hljs-keyword">from</span> llama_index.storage.storage_context <span class="hljs-keyword">import</span> StorageContext
<span class="hljs-keyword">from</span> llama_index.embeddings <span class="hljs-keyword">import</span> HuggingFaceEmbedding
<span class="hljs-keyword">from</span> traceloop.sdk <span class="hljs-keyword">import</span> Traceloop

openai.api_key = os.environ[<span class="hljs-string">"OPENAI_API_KEY"</span>]

<span class="hljs-comment"># Initialize Traceloop</span>
Traceloop.init()

chroma_client = chromadb.EphemeralClient()
chroma_collection = chroma_client.create_collection(<span class="hljs-string">"quickstart"</span>)

<span class="hljs-comment"># define embedding function</span>
embed_model = HuggingFaceEmbedding(model_name=<span class="hljs-string">"BAAI/bge-base-en-v1.5"</span>)

<span class="hljs-comment"># load documents</span>
documents = SimpleDirectoryReader(<span class="hljs-string">"./data/my_docs/"</span>).load_data()

<span class="hljs-comment"># set up ChromaVectorStore and load in data</span>
vector_store = ChromaVectorStore(chroma_collection=chroma_collection)
storage_context = StorageContext.from_defaults(vector_store=vector_store)
service_context = ServiceContext.from_defaults(embed_model=embed_model)
index = VectorStoreIndex.from_documents(
    documents, storage_context=storage_context, service_context=service_context
)

<span class="hljs-comment"># Query Data</span>
query_engine = index.as_query_engine()
response = query_engine.query(<span class="hljs-string">"Summarize the documents in context"</span>)
</code></pre>
<p>For more information refer to the <a target="_blank" href="https://www.traceloop.com/docs/openllmetry/introduction">Traceloop SDK Documentation</a></p>
<h2 id="heading-step-2-grafana-agent-sender">Step 2: Grafana Agent Sender</h2>
<p>Configure a <a target="_blank" href="https://grafana.com/docs/agent/latest/static/set-up/install/">Grafana Agent</a> instance to feed <strong>Traceloop</strong> traces into <strong>qryn</strong> / <strong>qryn.cloud</strong></p>
<pre><code class="lang-yaml"><span class="hljs-attr">traces:</span>
  <span class="hljs-attr">configs:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">default</span>
      <span class="hljs-attr">remote_write:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">endpoint:</span> <span class="hljs-string">&lt;Gigapipe</span> <span class="hljs-string">qryn.cloud</span> <span class="hljs-string">endpoint&gt;:443</span>
          <span class="hljs-string">basic_auth</span>
            <span class="hljs-attr">username:</span> <span class="hljs-string">&lt;Gigapipe</span> <span class="hljs-string">qryn</span> <span class="hljs-string">X-API-Key&gt;</span>
            <span class="hljs-attr">password:</span> <span class="hljs-string">&lt;Gigapipe</span> <span class="hljs-string">qryn</span> <span class="hljs-string">X-API-Secret&gt;</span>
      <span class="hljs-attr">receivers:</span>
        <span class="hljs-attr">otlp:</span>
          <span class="hljs-attr">protocols:</span>
            <span class="hljs-attr">grpc:</span>

<span class="hljs-string">/*</span> <span class="hljs-string">Environment</span> <span class="hljs-string">Variable</span> <span class="hljs-string">for</span> <span class="hljs-string">your</span> <span class="hljs-string">local</span> <span class="hljs-string">app</span> <span class="hljs-string">with</span> <span class="hljs-string">Traceloop</span> <span class="hljs-string">*/</span>
<span class="hljs-string">TRACELOOP_BASE_URL=http://&lt;grafana-agent-hostname&gt;:4318</span>
</code></pre>
<p><strong><mark>That's it! You're now ready to explore your LLMs activity using qryn</mark></strong></p>
<p>You can immediately get started with some popular examples:</p>
<h4 id="heading-trace-prompts-and-completionshttpsgithubcomtraceloopopenllmetryblobmainpackagessample-appsampleappopenaistreamingpy">👉 <a target="_blank" href="https://github.com/traceloop/openllmetry/blob/main/packages/sample-app/sample_app/openai_streaming.py">Trace prompts and completions</a></h4>
<p>Call OpenAI and see prompts, completions, and token usage for your call.</p>
<p>👉 <a target="_blank" href="https://github.com/traceloop/openllmetry/blob/main/packages/sample-app/sample_app/chroma_app.py">Trace your RAG retrieval pipeline</a></p>
<p>Build a RAG pipeline with Chroma and OpenAI. See vectors returned from Chroma, full prompt in OpenAI and responses</p>
<h3 id="heading-are-you-ready"><strong>Are you Ready?</strong></h3>
<p>Signup for a free account on <a target="_blank" href="https://qryn.cloud/"><strong>qryn.cloud</strong></a> or install our <a target="_blank" href="https://qryn.dev/"><strong>oss stack</strong></a> on-premise ⭐</p>
<p><a target="_blank" href="https://qryn.cloud/"><img src="https://user-images.githubusercontent.com/1423657/218818279-3efff74f-0191-498a-bdc4-f2650c9d3b49.gif" alt /></a></p>
]]></content:encoded></item><item><title><![CDATA[Embedded OLAP Benchmarks]]></title><description><![CDATA[Benchmarks for embedded SQL databases are rarely impartial and they tend to use larger-than-life configurations by each vendor in their race for the market peak. The typical megalomaniac benches ($$$) are usually both scientifically and technically a...]]></description><link>https://blog.gigapipe.com/embedded-olap-benchmarks</link><guid isPermaLink="true">https://blog.gigapipe.com/embedded-olap-benchmarks</guid><category><![CDATA[ClickHouse]]></category><category><![CDATA[duckDB]]></category><category><![CDATA[Benchmark]]></category><category><![CDATA[OLAP]]></category><category><![CDATA[github-actions]]></category><dc:creator><![CDATA[Lorenzo Mangani]]></dc:creator><pubDate>Mon, 13 Nov 2023 07:00:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1699799734329/dd84ef8e-f6c5-4d49-b11b-8c397c1cd17f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><img src="https://user-images.githubusercontent.com/1423657/278132064-ba8f08fe-49db-4f77-a2b5-71181e87233e.png" alt /></p>
<p>Benchmarks for embedded SQL databases are rarely impartial and they tend to use larger-than-life configurations by each vendor in their race for the market peak. The typical megalomaniac benches ($$$) are usually both scientifically and technically accurate but they rarely translate into anything useful for us regular end-users deploying their databases on the lowest tier options and using cheap resources.</p>
<p><em>With this in mind, we decided to assemble a simple and "fair" benchmarking tool</em></p>
<p>The rules for our little game are simple:</p>
<ul>
<li><p>No reliance on dedicated CPUs and fast RAM. <em>Cheap stuff only.</em></p>
</li>
<li><p>Focus on <em>real</em> network operations with <em>remote</em> parquet files.</p>
</li>
<li><p>Mimicking <em>real-life scenarios</em> and queries <em>(as a query collection)</em></p>
</li>
</ul>
<h3 id="heading-github-actions">GitHub Actions</h3>
<p>GitHub Actions runners are a great example of "cheap resources" <em>(as in free)</em> offering us 2vCPUs and 7GBs of RAM for each execution. We decided to use actions to benchmark our target databases equally sharing the pros and cons uniformly.</p>
<h3 id="heading-python">Python</h3>
<p>Python will be our playground since most embedded OLAP databases support it.</p>
<p>Our runner will orderly spawn the same SQL tests against each of the contending databases in separate parallel sessions operating with equal CPU/RAM/Network resources and remote parquet files with local tests for balance. No caching allowed.</p>
<p><img src="https://i.ytimg.com/vi/wW62ONjSA-0/maxresdefault.jpg?sqp=-oaymwEmCIAKENAF8quKqQMa8AEB-AHUBoAC4AOKAgwIABABGHIgVSgfMA8=&amp;rs=AOn4CLAQBnVaDIYrOLhXMbDD0T0uto5nSA" alt="A Few Hours Later… - SpongeBob Time Card - YouTube" /></p>
<p>A few hours later our first action-based <a target="_blank" href="https://github.com/lmangani/embedded-db-benchmarks">benchmark playground</a> was ready to run with tests covering our first group of embedded SQL OLAP engines:</p>
<ul>
<li><p><a target="_blank" href="https://doc.chdb.io"><strong>chdb</strong></a></p>
</li>
<li><p><a target="_blank" href="https://duckdb.org"><strong>duckdb</strong></a></p>
</li>
<li><p><a target="_blank" href="https://glaredb.com"><strong>glaredb</strong></a></p>
</li>
<li><p><strong>databend</strong></p>
</li>
</ul>
<p>⏩ If you want to fast-forward to the code and real action reports:</p>
<blockquote>
<p>GitHub Repo: <a target="_blank" href="https://github.com/lmangani/embedded-db-benchmarks">https://github.com/lmangani/embedded-db-benchmarks</a></p>
</blockquote>
<p>Test groups are executed on demand with a customizable number of iterations:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699795477171/86db3029-3220-401f-bd15-56b84b5fd00f.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-query-results">Query Results</h3>
<p>For each run and each database runner, separate query statistics are collected.</p>
<p>Here are some sample speed results for <strong>chdb</strong> <em>(0.15.0)</em> and <strong>duckdb</strong> <em>(0.9.1):</em></p>
<pre><code class="lang-python">Testing chdb (<span class="hljs-number">0.15</span><span class="hljs-number">.0</span>)
chdb:version: avg=<span class="hljs-number">0.018</span>s min=<span class="hljs-number">0.017</span>s max=<span class="hljs-number">0.020</span>s (<span class="hljs-number">3</span> runs)
chdb:count: avg=<span class="hljs-number">0.312</span>s min=<span class="hljs-number">0.210</span>s max=<span class="hljs-number">0.502</span>s (<span class="hljs-number">3</span> runs)
chdb:groupby: avg=<span class="hljs-number">0.772</span>s min=<span class="hljs-number">0.742</span>s max=<span class="hljs-number">0.824</span>s (<span class="hljs-number">3</span> runs)
chdb:groupby-local: avg=<span class="hljs-number">0.436</span>s min=<span class="hljs-number">0.432</span>s max=<span class="hljs-number">0.441</span>s (<span class="hljs-number">3</span> runs)

Testing duckdb (<span class="hljs-number">0.9</span><span class="hljs-number">.1</span>)
duckdb:version: avg=<span class="hljs-number">0.000</span>s min=<span class="hljs-number">0.000</span>s max=<span class="hljs-number">0.001</span>s (<span class="hljs-number">3</span> runs)
duckdb:count: avg=<span class="hljs-number">0.358</span>s min=<span class="hljs-number">0.120</span>s max=<span class="hljs-number">0.823</span>s (<span class="hljs-number">3</span> runs)
duckdb:groupby: avg=<span class="hljs-number">0.778</span>s min=<span class="hljs-number">0.769</span>s max=<span class="hljs-number">0.793</span>s (<span class="hljs-number">3</span> runs)
duckdb:groupby-local: avg=<span class="hljs-number">0.498</span>s min=<span class="hljs-number">0.494</span>s max=<span class="hljs-number">0.505</span>s (<span class="hljs-number">3</span> runs)
</code></pre>
<p>Each group runs all tests in parallel, making it easy to compare changes over time.</p>
<p>For instance, here's the latest <strong>chdb</strong> <em>(0.16.0rc2)</em> gaining speed on parquet counts 🔥</p>
<pre><code class="lang-python">Testing chdb (<span class="hljs-number">0.16</span><span class="hljs-number">.0</span>rc2)
chdb:version: avg=<span class="hljs-number">0.011</span>s min=<span class="hljs-number">0.011</span>s max=<span class="hljs-number">0.012</span>s (<span class="hljs-number">5</span> runs)
chdb:count: avg=<span class="hljs-number">0.160</span>s min=<span class="hljs-number">0.082</span>s max=<span class="hljs-number">0.386</span>s (<span class="hljs-number">5</span> runs) 🔥
chdb:groupby: avg=<span class="hljs-number">0.445</span>s min=<span class="hljs-number">0.407</span>s max=<span class="hljs-number">0.496</span>s (<span class="hljs-number">5</span> runs) 🔥
chdb:groupby-local: avg=<span class="hljs-number">0.338</span>s min=<span class="hljs-number">0.325</span>s max=<span class="hljs-number">0.344</span>s (<span class="hljs-number">5</span> runs) 🔥

Testing duckdb (<span class="hljs-number">0.9</span><span class="hljs-number">.1</span>)
duckdb:version: avg=<span class="hljs-number">0.000</span>s min=<span class="hljs-number">0.000</span>s max=<span class="hljs-number">0.000</span>s (<span class="hljs-number">5</span> runs) 🔥
duckdb:count: avg=<span class="hljs-number">0.259</span>s min=<span class="hljs-number">0.098</span>s max=<span class="hljs-number">0.894</span>s (<span class="hljs-number">5</span> runs)
duckdb:groupby: avg=<span class="hljs-number">0.571</span>s min=<span class="hljs-number">0.566</span>s max=<span class="hljs-number">0.576</span>s (<span class="hljs-number">5</span> runs)
duckdb:groupby-local: avg=<span class="hljs-number">0.341</span>s min=<span class="hljs-number">0.334</span>s max=<span class="hljs-number">0.351</span>s (<span class="hljs-number">5</span> runs)
</code></pre>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><em>CPU, Memory and Network utilization are also collected during tests</em></div>
</div>

<p>With each test, there's a pinch of luck involved. This is why multiple run are needed.</p>
<p>⚠️ <mark>Remember our bench does not want to be authoritative - it wants to be realistic!</mark></p>
<h3 id="heading-full-reports">Full Reports</h3>
<p>The benchmarking actions tracks the system resource utilization during tests.</p>
<p>For instance, we can see how <strong>chdb</strong> peaks its CPU utilization at 23% while <strong>duckdb</strong> reaches 36% within the same timeframe of execution. Pretty interesting 🔥</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699795950820/424aedeb-11bb-4bb2-8fef-34b9fcb74b82.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-first-conclusions">First Conclusions</h3>
<p>Here's one of the latest runs: <strong>chdb</strong>, <strong>duckdb</strong> followed by <strong>glaredb</strong> compete for the top positions while <strong>databend</strong> and <strong>datafusion</strong> results in the slowest and more memory hungry. <em>Network fluctuations and latency issues play a role - just like in real life!</em></p>
<p><mark>Do you think our tests are penalizing any engine? Shall we run multiple rounds and aggregate results? Please submit a PR or open an issue and we'll try anything!</mark></p>
<pre><code class="lang-python">Testing chdb <span class="hljs-number">0.16</span><span class="hljs-number">.0</span>rc2 (<span class="hljs-number">23.10</span><span class="hljs-number">.1</span><span class="hljs-number">.1</span>)
chdb:version: avg=<span class="hljs-number">0.012</span>s min=<span class="hljs-number">0.011</span>s max=<span class="hljs-number">0.014</span>s (<span class="hljs-number">3</span> runs) | Memory used: <span class="hljs-number">2.47</span> MB
chdb:count: avg=<span class="hljs-number">0.135</span>s min=<span class="hljs-number">0.064</span>s max=<span class="hljs-number">0.264</span>s (<span class="hljs-number">3</span> runs) | Memory used: <span class="hljs-number">3.91</span> MB
chdb:groupby: avg=<span class="hljs-number">0.435</span>s min=<span class="hljs-number">0.407</span>s max=<span class="hljs-number">0.478</span>s (<span class="hljs-number">3</span> runs) | Memory used: <span class="hljs-number">25.98</span> MB

Testing duckdb <span class="hljs-number">0.9</span><span class="hljs-number">.1</span>
duckdb:version: avg=<span class="hljs-number">0.001</span>s min=<span class="hljs-number">0.000</span>s max=<span class="hljs-number">0.001</span>s (<span class="hljs-number">3</span> runs) | Memory used: <span class="hljs-number">2.96</span> MB
duckdb:count: avg=<span class="hljs-number">0.360</span>s min=<span class="hljs-number">0.083</span>s max=<span class="hljs-number">0.900</span>s (<span class="hljs-number">3</span> runs) | Memory used: <span class="hljs-number">26.02</span> MB
duckdb:groupby: avg=<span class="hljs-number">0.697</span>s min=<span class="hljs-number">0.685</span>s max=<span class="hljs-number">0.715</span>s (<span class="hljs-number">3</span> runs) | Memory used: <span class="hljs-number">25.86</span> MB

Testing glaredb <span class="hljs-number">0.5</span><span class="hljs-number">.1</span>
glaredb:version: avg=<span class="hljs-number">0.001</span>s min=<span class="hljs-number">0.000</span>s max=<span class="hljs-number">0.001</span>s (<span class="hljs-number">3</span> runs) | Memory used: <span class="hljs-number">11.38</span> MB
glaredb:count: avg=<span class="hljs-number">0.157</span>s min=<span class="hljs-number">0.071</span>s max=<span class="hljs-number">0.307</span>s (<span class="hljs-number">3</span> runs) | Memory used: <span class="hljs-number">9.00</span> MB
glaredb:groupby: avg=<span class="hljs-number">0.489</span>s min=<span class="hljs-number">0.482</span>s max=<span class="hljs-number">0.496</span>s (<span class="hljs-number">3</span> runs) | Memory used: <span class="hljs-number">200.90</span> MB

Testing databend <span class="hljs-number">1.2</span><span class="hljs-number">.207</span>
databend:version: avg=<span class="hljs-number">0.013</span>s min=<span class="hljs-number">0.001</span>s max=<span class="hljs-number">0.038</span>s (<span class="hljs-number">3</span> runs) | Memory used: <span class="hljs-number">3.50</span> MB
databend:count: avg=<span class="hljs-number">0.237</span>s min=<span class="hljs-number">0.216</span>s max=<span class="hljs-number">0.277</span>s (<span class="hljs-number">3</span> runs) | Memory used: <span class="hljs-number">7.50</span> MB
databend:groupby: avg=<span class="hljs-number">1.629</span>s min=<span class="hljs-number">1.580</span>s max=<span class="hljs-number">1.674</span>s (<span class="hljs-number">3</span> runs) | Memory used: <span class="hljs-number">462.03</span> MB

Testing datafusion <span class="hljs-number">32.0</span><span class="hljs-number">.0</span>
datafusion:version: avg=<span class="hljs-number">0.016</span>s min=<span class="hljs-number">0.001</span>s max=<span class="hljs-number">0.045</span>s (<span class="hljs-number">3</span> runs) | Memory used: <span class="hljs-number">3.62</span> MB
datafusion:count: avg=<span class="hljs-number">0.243</span>s min=<span class="hljs-number">0.179</span>s max=<span class="hljs-number">0.338</span>s (<span class="hljs-number">3</span> runs) | Memory used: <span class="hljs-number">7.12</span> MB
datafusion:groupby: avg=<span class="hljs-number">1.860</span>s min=<span class="hljs-number">1.820</span>s max=<span class="hljs-number">1.920</span>s (<span class="hljs-number">3</span> runs) | Memory used: <span class="hljs-number">474.79</span> MB
</code></pre>
<h3 id="heading-cloudbench">CloudBench</h3>
<p>This way of testing is showing potential, but why should we reinvent the wheel? Due to popular demand, we're porting our action-based benchmarks to <strong>ClickBench</strong></p>
<p><a target="_blank" href="https://lmangani.github.io/CloudBench/#eyJzeXN0ZW0iOnsiY2hkYiI6dHJ1ZSwiZGF0YWJlbmQiOnRydWUsIkR1Y2tEQiI6dHJ1ZSwiZ2xhcmVkYiI6dHJ1ZX0sInR5cGUiOnsiQysrIjp0cnVlLCJjb2x1bW4tb3JpZW50ZWQiOnRydWUsIkNsaWNrSG91c2UgZGVyaXZhdGl2ZSI6dHJ1ZSwiZW1iZWRkZWQiOnRydWUsImdpdGh1YiI6dHJ1ZX0sIm1hY2hpbmUiOnsiZ2l0aHViX3J1bm5lciAydkNQVV83R0IiOnRydWV9LCJjbHVzdGVyX3NpemUiOnsic2VydmVybGVzcyI6dHJ1ZX0sIm1ldHJpYyI6ImhvdCIsInF1ZXJpZXMiOlt0cnVlLHRydWUsdHJ1ZSx0cnVlLHRydWVdfQ=="><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700130025251/42166a05-cffe-4a85-8664-a994132614de.png" alt class="image--center mx-auto" /></a></p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><em><mark>Resilience to network fluctuations &amp; latency is an integral part of the test</mark></em></div>
</div>

<h3 id="heading-observability">Observability</h3>
<p>The reports are still quite generic and provide useful insight into test execution directly from our actions, without any cost or any external service required. But to determine anything scientific we need to collect thousands of executions over time.</p>
<p>To track and analyze the performance of each test over time we can configure our Reports to be shipped as complete <strong>logs, metrics</strong> and <strong>traces</strong> directly into <a target="_blank" href="https://qryn.cloud">qryn</a> 👇👇</p>
<p><a target="_blank" href="https://qryn.cloud"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1699799618952/d53bd0be-17e2-482e-ab7c-c5d0d20ffc9e.png" alt class="image--center mx-auto" /></a></p>
]]></content:encoded></item><item><title><![CDATA[OpenAI Observability]]></title><description><![CDATA[If you are building Applications with OpenAI's API this post is for you!

Introduction
In an era where AI and machine learning are at the forefront of technological advancements, services like OpenAI and ChatGPT have gained immense popularity for the...]]></description><link>https://blog.gigapipe.com/openai-observability</link><guid isPermaLink="true">https://blog.gigapipe.com/openai-observability</guid><category><![CDATA[openai]]></category><category><![CDATA[chatgpt]]></category><category><![CDATA[observability]]></category><category><![CDATA[monitoring]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Lorenzo Mangani]]></dc:creator><pubDate>Fri, 03 Nov 2023 10:23:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1698974506089/5eaaf9dc-4a22-426c-94fb-73a51993521c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><img src="https://allvectorlogo.com/img/2017/07/openai-logo.png" alt /></p>
<blockquote>
<p>If you are building Applications with <strong>OpenAI's API</strong> this post is for you!</p>
</blockquote>
<h3 id="heading-introduction">Introduction</h3>
<p>In an era where <strong>AI</strong> and <strong>machine learning</strong> are at the forefront of technological advancements, services like <strong>OpenAI</strong> and <strong>ChatGPT</strong> have gained immense popularity for their ability to transform industries, streamline customer interactions, and automate various processes.</p>
<p>However, <strong><em>with great power comes great responsibility</em></strong>, and monitoring and observability are vital aspects of ensuring the smooth operation and optimal utilization of such AI services, and avoiding <em>unpleasant surprises</em> <em>(=costs)</em></p>
<p>In this quick article, we will explore how the <a target="_blank" href="http://qryn.cloud">qryn.cloud</a> polyglot observability stack can collect <strong>Metrics</strong> and <strong>Logs</strong> to provide valuable insights into the performance, behavior, and usage patterns of <strong>OpenAI</strong> and <strong>ChatGPT</strong>, enabling organizations to harness the full platform potential while ensuring <em>reliability and efficiency.</em></p>
<h3 id="heading-benefits-of-monitoring-openai">Benefits of Monitoring OpenAI ✨</h3>
<p>Here are some <strong>good reasons</strong> to Monitor OpenAI <em>- as explained by ChatGPT:</em></p>
<ol>
<li><p><strong>Performance Optimization:</strong> Effective monitoring allows organizations to keep a close eye on the performance of OpenAI and ChatGPT. By collecting and analyzing metrics, businesses can identify bottlenecks, latency issues, and areas where optimization is needed. This, in turn, leads to improved response times and enhanced user experiences.</p>
</li>
<li><p><strong>Cost Management:</strong> Running AI models like ChatGPT can be resource-intensive. Through comprehensive monitoring, you can gain insights into usage patterns and cost trends. This data enables informed decisions about resource allocation, helping organizations optimize their budget and prevent unexpected overages.</p>
</li>
<li><p><strong>User Experience Enhancement:</strong> Understanding how users interact with ChatGPT and OpenAI is crucial for delivering a seamless user experience. Observing user behavior and analyzing logs can uncover pain points, frequently asked questions, and other valuable insights to tailor AI responses and services to better meet user needs.</p>
</li>
<li><p><strong>Security and Compliance:</strong> Security is a top concern when dealing with sensitive data or information. Effective monitoring helps detect and prevent security breaches, unauthorized access, and potential vulnerabilities. It also aids in ensuring compliance with data protection regulations and industry standards.</p>
</li>
<li><p><strong>Predictive Maintenance:</strong> Proactive monitoring can help identify issues before they become critical. By setting up alerts for anomalies and unusual behavior, organizations can implement preventive measures, reducing downtime and the risk of service disruptions.</p>
</li>
<li><p><strong>Scaling and Resource Allocation:</strong> As demand for AI services fluctuates, organizations need to be agile in scaling resources. Monitoring helps in understanding usage patterns and trends, enabling efficient resource allocation and scaling to meet demand, whether for peak hours or seasonal changes.</p>
</li>
<li><p><strong>Customization and Improvement:</strong> Observability data can provide insights into how users are interacting with AI models. This information can be used to refine and customize AI responses, improving the quality of interactions and ultimately enhancing user satisfaction.</p>
</li>
</ol>
<h3 id="heading-requirements">Requirements</h3>
<blockquote>
<p>For this experiment we can use any <strong>Linux</strong> system with <strong>Python3.x</strong>.</p>
</blockquote>
<p>Before starting, install the <strong>grafana-openai-monitoring</strong> dependency using <strong>pip</strong></p>
<pre><code class="lang-bash">pip install grafana-openai-monitoring
</code></pre>
<p>We will need a couple of tokens to configure our monitoring script:</p>
<ol>
<li><p><strong>OpenAI API Key</strong></p>
</li>
<li><p><strong>Scoped Token for qryn.cloud</strong> <em>(not needed for qryn oss)</em></p>
</li>
</ol>
<h3 id="heading-its-monitoring-time">It's Monitoring Time 🔭</h3>
<p>To monitor <strong>Chat completions</strong> using the OpenAI API, you can use the <code>chat_v2.monitor</code> decorator. This decorator automatically tracks <strong>API calls</strong> and sends <strong>metrics</strong> and <strong>logs</strong> to the qryn or qryn.cloud endpoints.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> openai
<span class="hljs-keyword">from</span> grafana_openai_monitoring <span class="hljs-keyword">import</span> chat_v2

<span class="hljs-comment"># Set your OpenAI API key</span>
openai.api_key = <span class="hljs-string">"YOUR_OPEN_AI_API_KEY"</span>

<span class="hljs-comment"># Apply the custom decorator to the OpenAI API function</span>
openai.ChatCompletion.create = chat_v2.monitor(
  openai.ChatCompletion.create,
  metrics_url=<span class="hljs-string">"https://qryn.gigapipe.com/api/v1/prom/remote/write"</span>,
  logs_url=<span class="hljs-string">"https://qryn.gigapipe.com/loki/api/v1/push"</span>,
  metrics_username=<span class="hljs-string">"X-API-Key"</span>,
  logs_username=<span class="hljs-string">"X-API-Key"</span>,
  access_token=<span class="hljs-string">"X-API-Secret"</span>
  )

<span class="hljs-comment"># Now any call to openai.ChatCompletion.create will be automatically tracked</span>
response = openai.ChatCompletion.create(model=<span class="hljs-string">"gpt-4"</span>, max_tokens=<span class="hljs-number">100</span>, messages=[{<span class="hljs-string">"role"</span>: <span class="hljs-string">"user"</span>, <span class="hljs-string">"content"</span>: <span class="hljs-string">"What is Observability?"</span>}])

print(response)
</code></pre>
<p>To monitor <strong>Completions</strong> using the OpenAI API, you can use the <code>chat_v1.monitor</code> decorator. This decorator adds monitoring capabilities to the <strong>OpenAI API</strong> function and sends <strong>metrics</strong> and <strong>logs</strong> to the qryn or qryn.cloud endpoints.</p>
<pre><code class="lang-bash">import openai
from grafana_openai_monitoring import chat_v1

<span class="hljs-comment"># Set your OpenAI API key</span>
openai.api_key = <span class="hljs-string">"YOUR_OPEN_AI_API_KEY"</span>

<span class="hljs-comment"># Apply the custom decorator to the OpenAI API function</span>
openai.Completion.create = chat_v1.monitor(
  openai.Completion.create,
  metrics_url=<span class="hljs-string">"https://qryn.gigapipe.com/api/v1/prom/remote/write"</span>,
  logs_url=<span class="hljs-string">"https://qryn.gigapipe.com/loki/api/v1/push"</span>,
  metrics_username=<span class="hljs-string">"X-API-Key"</span>,
  logs_username=<span class="hljs-string">"X-API-Key"</span>,
  access_token=<span class="hljs-string">"X-API-Secret"</span>
  )

<span class="hljs-comment"># Now any call to openai.Completion.create will be automatically tracked</span>
response = openai.Completion.create(model=<span class="hljs-string">"davinci"</span>, max_tokens=100, prompt=<span class="hljs-string">"What is Observability?"</span>)

<span class="hljs-built_in">print</span>(response)
</code></pre>
<p><em>After configuring the parameters, the monitored API function will automatically log and track the requests and responses to the specified endpoints.</em></p>
<h3 id="heading-grafana-dashboard">Grafana Dashboard 🛸</h3>
<p>Once our data is ingested in <strong>qryn</strong> or <strong>qryn.cloud</strong>, we can <a target="_blank" href="https://gist.github.com/lmangani/8bff9c79a1fe7bf7e9b7b3c2a4e7cacc#file-grafana_openai_dashboard-json">download and import the OpenAI Dashboard</a> in our connected Grafana instance to <em>display metrics and logs.</em></p>
<p><a target="_blank" href="https://qryn.cloud"><img src="https://grafana.com/media/blog/monitor-openai/openai-dashboard-grafana-cloud-2.png" alt class="image--center mx-auto" /></a></p>
<h3 id="heading-potential-unlocked">Potential Unlocked 🔥</h3>
<p>Monitoring OpenAI and ChatGPT with the <a target="_blank" href="http://qryn.cloud">qryn.cloud</a> polyglot observability stack is not just about tracking metrics and logs; it's about <strong>unlocking the full potential of AI services</strong> while ensuring <strong>reliability, security, and cost-effectiveness</strong>. With the right observability tools and practices in place, organizations can harness the power of AI to its fullest, staying competitive in a rapidly evolving technological landscape.</p>
<h3 id="heading-are-you-ready">Are you Ready?</h3>
<p>Signup for a free account on <a target="_blank" href="https://qryn.cloud">qryn.cloud</a> or install our <a target="_blank" href="https://qryn.dev">oss stack</a> on-premise ⭐</p>
<p><a target="_blank" href="https://qryn.cloud"><img src="https://user-images.githubusercontent.com/1423657/218818279-3efff74f-0191-498a-bdc4-f2650c9d3b49.gif" alt class="image--center mx-auto" /></a></p>
]]></content:encoded></item></channel></rss>