<?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" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Pavol Loffay on Medium]]></title>
        <description><![CDATA[Stories by Pavol Loffay on Medium]]></description>
        <link>https://medium.com/@ploffay?source=rss-a20a4172f9c------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*Bfzuf2xlBzSXmZ6g.</url>
            <title>Stories by Pavol Loffay on Medium</title>
            <link>https://medium.com/@ploffay?source=rss-a20a4172f9c------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 21 Jul 2026 23:48:39 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@ploffay/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Building AI Travel app in 2026: From Figma to the App Store in a Day]]></title>
            <link>https://ploffay.medium.com/building-ai-travel-app-in-2026-from-figma-to-the-app-store-in-a-day-a1d0049fb2a2?source=rss-a20a4172f9c------2</link>
            <guid isPermaLink="false">https://medium.com/p/a1d0049fb2a2</guid>
            <category><![CDATA[travel-ai]]></category>
            <category><![CDATA[agentic-ai]]></category>
            <category><![CDATA[google-antigravity]]></category>
            <category><![CDATA[travel-tips]]></category>
            <category><![CDATA[travel]]></category>
            <dc:creator><![CDATA[Pavol Loffay]]></dc:creator>
            <pubDate>Sun, 15 Mar 2026 22:05:12 GMT</pubDate>
            <atom:updated>2026-03-16T12:54:15.771Z</atom:updated>
            <content:encoded><![CDATA[<p>2025 was a year of transformation. I went from writing every line of code manually in January to building a full mobile app MVP in a single week by December, thanks to a completely new AI-driven workflow.</p><p>In this blog post, I want to share how I built <a href="https://nunutravelguide.com/"><strong>nunutravelguide.com</strong></a> over the Christmas break. One of my first AI use cases was actually a personal one: travel. Whenever I visit a new place, I love diving into the local culture and history — I want to learn the stories behind the city, not just see the sights.</p><p>While visiting Madrid in 2024, I started using Gemini to generate my own audio guides. The museums were packed, and I could never get close enough to read the descriptions. Instead, I took photos of the exhibits and asked Gemini to explain them to me. It worked incredibly well! The best part was the ability to ask follow-up questions and dig deeper into specific historical concepts.</p><p>A year later, AI coding tools have advanced so much that I decided to turn this workflow into a dedicated app. As a year-end challenge for 2025, I built a custom audio travel guide to solve this exact problem.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ntnGhx_5d5NnXluiU2QCyQ.png" /></figure><h3>Start with the UI</h3><p>Since I had zero experience with front-end development or building mobile apps, I knew I needed a plan. My biggest challenge was figuring out how to automate or generate the bulk of the code.</p><p>I had heard that Figma recently launched an AI integration called <strong>Figma Make</strong>. Since I wasn’t familiar with many other tools, I dove right in. Figma Make worked incredibly well — after about 150 iterations and prompts, I finally landed on a design I loved. Best of all, it generated a mobile-responsive site that served as the perfect foundation for my app!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KWgsyJirQJYXi1KnpAiINg.png" /></figure><h3>From the website to the mobile app</h3><p>I had made great progress — within a few hours, I had a working website. But wait… a website? I needed a mobile app! My goal was to create a cross-platform app for both <strong>Android</strong> and <strong>iOS</strong>. The next step was to scaffold a React Native project and begin the transformation.</p><p>I discovered the <strong>react-native-template-obytes</strong>, cloned it, set up the Android tooling, and successfully deployed it to an emulator. Success! However, I still had no idea how to actually port the website code to the mobile app, and I definitely didn’t want to do it manually.</p><p>I initially tried using a <strong>Figma MCP</strong> (Model Context Protocol), but the workflow was clunky. I had to take screenshots of my Figma Make designs and paste them into a design kit just so the AI agent could “see” them. Even then, static images couldn’t capture the button logic that was already working on my website.</p><p>After some brainstorming, I tried a different shortcut: I downloaded the website’s source code and asked my AI agent to rewrite the entire thing in React Native. Within minutes, I had a functional mobile app. Amazing!</p><h3>AI, Text-to-Speech (TTS), and Interactive Maps</h3><p>My next challenge was using Generative AI to create the guide text and feeding it into a Text-to-Speech (TTS) API to generate the audio. While this was relatively simple with the <strong>Firebase API</strong>, I eventually transitioned to using OpenAI with a <strong>Google Cloud Run</strong> proxy to protect my API keys.</p><p>Setting up the Cloud Run function was incredibly straightforward. I even discovered that it’s built on top of the open-source <strong>Knative</strong> project, which I had used in the past!</p><p>For the mapping component, I chose <strong>Mapbox</strong> for its impressive 3D map view. To populate the guide, I pulled landmarks and points of interest directly from the Mapbox API and <strong>Wikidata</strong>.</p><h3>RevenueCat and In-App Purchases</h3><p>While the initial prototype took roughly one weekend, I spent several more days refining the app, adding features, and creating production builds. Integrating <strong>RevenueCat</strong> and configuring in-app purchases took about a full day of work, especially since it was my first time setting them up.</p><h3><strong>Antigravity and the AI Tech Stack</strong></h3><p>The app was built using the <strong>Antigravity AI IDE</strong> and <strong>Gemini 3.0/3.1</strong> models. The IDE is incredibly intuitive, particularly its Agent Manager and Plan Mode for complex tasks. I really enjoyed the workflow of editing the plan by adding comments — it felt very similar to a GitHub code review.</p><h3>Conclusion</h3><p>Indeed, 2025 has truly transformed the way we work. With LLMs, we can now create production-ready software from scratch in a matter of days — even without prior experience in a specific stack. As a backend developer specialized in Golang and Java, I had zero UI or mobile development experience before this. For this project, I intentionally didn’t write a single line of code myself just to see what was possible.</p><p>I have to admit, it wasn’t always smooth sailing. The AI made plenty of mistakes and failed miserably at times. In one instance, it even tried to convince me there was a “known bug” in the iOS audio player, when the issue was actually in my own backend service! I frequently had to revert the code and start over from a previous Git checkout.</p><p>Despite those hurdles, it was an incredible learning experience. Testing the boundaries of current AI capabilities has been eye-opening, and I’m genuinely excited to see what we’ll be able to build in the future.</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FBuDOV1PsZWA%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fshorts%2FBuDOV1PsZWA&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FBuDOV1PsZWA%2Fhq2.jpg&amp;type=text%2Fhtml&amp;schema=youtube" width="640" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/7646ec550af5c32f06165fdbb564fbb5/href">https://medium.com/media/7646ec550af5c32f06165fdbb564fbb5/href</a></iframe><figure><img alt="" src="https://cdn-images-1.medium.com/max/1008/1*dCTQ757Y5Rh7TY_hw2sGdw.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1008/1*7aQdGJqHSgPhlu7_0KDZvg.jpeg" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a1d0049fb2a2" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Using OpenTelemetry auto-instrumentation/agents in Kubernetes]]></title>
            <link>https://medium.com/opentelemetry/using-opentelemetry-auto-instrumentation-agents-in-kubernetes-869ec0f42377?source=rss-a20a4172f9c------2</link>
            <guid isPermaLink="false">https://medium.com/p/869ec0f42377</guid>
            <category><![CDATA[opentelemetry]]></category>
            <category><![CDATA[monitoring]]></category>
            <category><![CDATA[tracing]]></category>
            <category><![CDATA[distributed-tracing]]></category>
            <category><![CDATA[observability]]></category>
            <dc:creator><![CDATA[Pavol Loffay]]></dc:creator>
            <pubDate>Mon, 22 Nov 2021 17:16:06 GMT</pubDate>
            <atom:updated>2021-11-22T17:16:06.848Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hzEKhutNihfxb9yNXslEnA.jpeg" /></figure><p>In this article, I would like to introduce OpenTelemetry Operator’s new feature that significantly simplifies instrumenting workloads deployed on Kubernetes.</p><p>Instrumentation is the most tedious process when deploying an observability solution. There are multiple approaches how to instrument and application:</p><ul><li>manual/explicit: source code is explicitly instrumented (e.g. by using OpenTelemetry API) or by using pre-built instrumentation libraries that are linked at compile time.</li><li>automatic: application is instrumented without any code modifications and recompilation of the application is not needed.</li></ul><p>The automatic instrumentation was for a long time only available as a proprietary technology offered by various APM/observability vendors. OpenTelemetry changed this paradigm and made this technology available in open-source. Users get vendor-neutral instrumentation to avoid vendor lock-in for the most crucial part of the observability integration.</p><p>However, deploying auto-instrumentation at scale or validating proof of value can still be a tedious problem. Modern applications are packaged in immutable container images, hence adding instrumentation to already built images requires container rebuild. Rebuilding a large number of application images requires a major investment. Let’s have a look at how this problem can be solved on Kubernetes.</p><h3>Instrumentation CR in OpenTelemetry operator</h3><p>The OpenTelemetry Operator 0.38.0 introduced Instrumentation custom resource (CR) which defines the configuration for OpenTelemetry SDK and instrumentation. The instrumentation is enabled when an Instrumentation CR is present in the cluster and a namespace or workload is annotated:</p><pre>kubectl apply -f - &lt;&lt;EOF<br>apiVersion: opentelemetry.io/v1alpha1<br>kind: Instrumentation<br>metadata:<br>  name: my-instrumentation<br>spec:<br>  exporter:<br>    endpoint: http://otel-collector:4317<br>  propagators:<br>    - tracecontext<br>    - baggage<br>    - b3<br>  sampler:<br>    type: parentbased_traceidratio<br>    argument: &quot;0.25&quot;<br>  java:<br>    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-java:latest<br>  nodejs:<br>    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-nodejs:latest<br>  python:<br>    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-python:latest<br>EOF</pre><p>At the moment the instrumentation is supported for Java, NodeJS, and Python languages. The instrumentation is enabled when the following annotation is applied to a workload or a namespace.</p><ul><li>instrumentation.opentelemetry.io/inject-java: &quot;true&quot; — for Java</li><li>instrumentation.opentelemetry.io/inject-nodejs: &quot;true&quot; — for NodeJS</li><li>instrumentation.opentelemetry.io/inject-python: &quot;true&quot; — for Python</li></ul><p>After the annotation is applied the operator injects OpenTelemetry auto-instrumentation libraries into the application container and configures the instrumentation to export the data to an endpoint defined in the Instrumentation CR.</p><h3>Java example with Spring Petclinic</h3><p>Now let’s deploy the Spring Petclinic Java application that will be instrumented and report data to an OpenTelemetry collector. The collector will log spans to the standard output.</p><p>Create the following deployment:</p><pre>apiVersion: apps/v1<br>kind: Deployment<br>metadata:<br>  name: spring-petclinic<br>spec:<br>  selector:<br>    matchLabels:<br>      app: spring-petclinic<br>  replicas: 1<br>  template:<br>    metadata:<br>      labels:<br>        app: spring-petclinic<br>      annotations:<br>        sidecar.opentelemetry.io/inject: &quot;true&quot;<br>        instrumentation.opentelemetry.io/inject-java: &quot;true&quot;<br>    spec:<br>      containers:<br>      - name: app<br>        image: ghcr.io/pavolloffay/spring-petclinic:latest</pre><p>and apply the instrumentation annotation:</p><pre>kubectl patch deployment.apps/spring-petclinic -p &#39;{&quot;spec&quot;: {&quot;template&quot;: {&quot;metadata&quot;: {&quot;annotations&quot;: {&quot;instrumentation.opentelemetry.io/inject-java&quot;: &quot;true&quot;}}}}}&#39;</pre><p>After the annotation is applied the spring-petclinic pod will restart and the newly started pod will be instrumented with OpenTelemetry Java auto-instrumentation. The data will be reported in OTLP format to the collector at the address http://otel-collector:4317. The following code snippet deploys an OpenTelemetry collector that logs traces to the standard output.</p><pre>kubectl apply -f - &lt;&lt;EOF<br>apiVersion: opentelemetry.io/v1alpha1<br>kind: OpenTelemetryCollector<br>metadata:<br>  name: otel<br>spec:<br>  config: |<br>    receivers:<br>      otlp:<br>        protocols:<br>          grpc:<br>          http:<br>    processors:<br><br>    exporters:<br>      logging:<br><br>    service:<br>      pipelines:<br>        traces:<br>          receivers: [otlp]<br>          processors: []<br>          exporters: [logging]<br>EOF</pre><p>Now we can port-forward the application HTTP port via kubectl port-forward deployment.apps/spring-petclinic 8080:8080 and explore the application in WEB browser. The spans should be reported to the OpenTelemetry collector. The reported spans can be viewed via kubectl logs deployment.apps/otel-collector.</p><p>Auto-instrumentation is rather a complicated piece of software, the implementation depends on the language. In Java, the auto-instrumentation is called Javaagent and it does bytecode manipulation that injects instrumentation points to specific code paths. Then the instrumentation points create telemetry data (e.g. traces) when the code is executed. The auto-instrumentation is always supported for a defined set of frameworks or APIs. <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/docs/supported-libraries.md#supported-libraries-frameworks-application-servers-and-jvms">This is the list of supported frameworks and application servers for Java</a>.</p><h3>How is the injection logic implemented?</h3><p>You might wonder how the injection of auto-instrumentation is implemented and especially how the operator can configure the application to use it. Let me explain it for Java, however, similar concepts are used for other runtimes as well.</p><p>The OpenTelemetry operator implements <a href="https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/">mutating admission webhook</a> that is invoked when the Pod object is created or updated. The webhook modifies the Pod object to inject auto-instrumentation libraries into the application container, it configures OpenTelemetry SDK and runtime, in this case, Java virtual machine (JVM) to use the auto-instrumentation.</p><p>The auto-instrumentation library (Javaagent) is injected into the application container via an init container that copies the Javaagent into a volume that is mounted to the application container. The SDK configuration is done by injecting environment variables into the application container.</p><p>Now the final step is to configure the JVM to use the Javaagent. This is done by configuring the environment variable <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/envvars002.html">JAVA_TOOL_OPTIONS</a> to use the Javaagent.</p><h3>Conclusion</h3><p>We have seen that using OpenTelemetry auto-instrumentation on Kubernetes workloads can be simplified with the operator pattern. No change to application code or container images is required. This approach brings huge value for quickly validating telemetry solutions. At the moment only Java, Python, and NodeJS runtimes are supported, however <a href="https://github.com/open-telemetry/opentelemetry-operator/issues/455">support for other languages is coming</a>. The statically compiled languages like Golang or C++ can still use this feature to dynamically configure SDK. This essentially means the Instrumentation CR defines a control plane for OpenTelemtry SDK and instrumentations.</p><h3>References</h3><ul><li>OpenTelemetry Operator: <a href="https://github.com/open-telemetry/opentelemetry-operator#opentelemetry-auto-instrumentation-injection">https://github.com/open-telemetry/opentelemetry-operator#opentelemetry-auto-instrumentation-injection</a></li><li>Instrumentation CR docs: <a href="https://github.com/open-telemetry/opentelemetry-operator/blob/main/docs/api.md#instrumentation">https://github.com/open-telemetry/opentelemetry-operator/blob/main/docs/api.md#instrumentation</a></li><li>Supported frameworks in Java auto-instrumentation: <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/docs/supported-libraries.md#supported-libraries-frameworks-application-servers-and-jvms">https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/docs/supported-libraries.md#supported-libraries-frameworks-application-servers-and-jvms</a></li><li>Spring Pet Clinic: <a href="https://github.com/spring-projects/spring-petclinic">https://github.com/spring-projects/spring-petclinic</a></li><li>Kubernetes admission webhook: <a href="https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/">https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/</a></li><li>Java tools options: <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/envvars002.html">https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/envvars002.html</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=869ec0f42377" width="1" height="1" alt=""><hr><p><a href="https://medium.com/opentelemetry/using-opentelemetry-auto-instrumentation-agents-in-kubernetes-869ec0f42377">Using OpenTelemetry auto-instrumentation/agents in Kubernetes</a> was originally published in <a href="https://medium.com/opentelemetry">OpenTelemetry</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Five years evolution of open-source distributed tracing]]></title>
            <link>https://ploffay.medium.com/five-years-evolution-of-open-source-distributed-tracing-ec1c5a5dd1ac?source=rss-a20a4172f9c------2</link>
            <guid isPermaLink="false">https://medium.com/p/ec1c5a5dd1ac</guid>
            <category><![CDATA[zipkin]]></category>
            <category><![CDATA[observability]]></category>
            <category><![CDATA[monitoring]]></category>
            <category><![CDATA[jaegertracing]]></category>
            <category><![CDATA[opentelemetry]]></category>
            <dc:creator><![CDATA[Pavol Loffay]]></dc:creator>
            <pubDate>Sun, 27 Jun 2021 06:56:05 GMT</pubDate>
            <atom:updated>2021-06-28T08:58:40.431Z</atom:updated>
            <content:encoded><![CDATA[<p>It has been 5 years since I have started working on open-source (OSS) distributed tracing projects. It is certainly a long time and in this article I would like to summarize what I have learned and show you the evolution in OSS distributed tracing space and peek into the future. We will talk about projects like Hawkular-APM, Zipkin, Jaeger, OpenTracing, OpenTelemetry, SigNoz and Hypertrace. Quite a lot right!? So let’s start!</p><p>First let’s have a look at the timeline when these projects were open-sourced:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/789/1*njC65yXy2Za9kaaL_ICVmg.png" /></figure><p>Note that the timeline and article does not contain all OSS distributed tracing projects and all the content represents my subjective view on this space. There is missing OpenCensus, Expedia Haystack and many more. The first pull request to OpenCensus dates in 2016, but the project was based on existing Google’s <a href="https://opensource.google/projects/instrumentation-java">instrumentation-java project.</a></p><h3>2016: The beginning with Hawkular and Zipkin</h3><p>For me it all started in June 2016 when I submitted my first pull request to the <a href="https://github.com/hawkular/hawkular-apm">Hawkular APM project</a>. Many of you probably don’t know the project so let me give you a quick introduction. Hawkular APM is a performance monitoring tool. It is a “lightweight” Java EE application deployed on Wildfly and it heavily uses JMS, Infinispan and Elasticsearch as a storage backend. The major differentiator between Zipkin and other similar tools is that the project focuses more on data aggregation. For instance it provides service metrics visualisations, flow diagrams, ability to compare two services (e.g. canary deployment) and much more.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*SaunHcLCgHv1351-" /></figure><p>The project also provided native and OpenTracing based instrumentation libraries. Building instrumentation clients is an enormous effort, there are a lot of languages, frameworks and application runtimes to support. Hence we have started looking at existing solutions, more about this in the Zipkin section.</p><p>For me the Hawkular project was quite a short party and the project was sunset in mid 2017 for several reasons. To make a long story short, we had decided to join forces with Jaeger Tracing project which seemed like a better fit for the Kubernetes ecosystem that we were pursuing.</p><h3>2016: Zipkin</h3><p>In 2016 Zipkin was the only robust OSS tracing tool with a solid and proven ecosystem. The project was <a href="https://blog.twitter.com/engineering/en_us/a/2012/distributed-systems-tracing-with-zipkin">open-sourced in 2012 by Twitter</a>. The original version was written in Scala and later in 2016 rewritten to Java (Spring Boot). The design of the system was inspired by Google’s <a href="https://research.google/pubs/pub36356/">Dapper paper</a> released in 2010.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*UXGG-4iOO4qx4GO-" /></figure><p>The ecosystem was robust, it had a lot of instrumentation libraries, just to name a few: brave, zipkin-js, zipkin-ruby, zipkin-go and many more. The beauty of the Zipkin project was its technical excellence in Java, simple and stable data model, its own <a href="https://github.com/openzipkin/b3-propagation">B3</a> context propagation (it stands for Big Brother Bird, the original Zipkin name at Twitter) and great API level documentation. All these aspects made it clear that Zipkin was set to create a rich ecosystem. This was all great and in the Hawkular project we had decided to expose Zipkin ingestion endpoint that would allow existing Zipkin users to try out Hawkular and vice-versa Hawkular users leverage Zipkin instrumentation libraries.</p><p><a href="https://github.com/hawkular/hawkular-apm/tree/master/examples/polyglot-zipkin">I did a proof of concept</a> — a polyglot application written in Java, Ruby, Javascript and Python to test out instrumentation interoperability. I was very surprised to find out that some instrumentations <a href="https://github.com/hawkular/hawkular-apm/tree/738377a367d10cc479e1abd0374e799b03e8e740/examples/polyglot-zipkin#known-issues">did not propagate or decode the context properly</a>. This was a huge problem resulting in broken traces. The other issue was that the instrumentation libraries were not using tags coherently. For instance, some libraries were recording full URLs and others only HTTP path with query parameters. The data quality and consistency is very important for an observability platform to work properly.</p><p>Ultimately these issues were fixed over time, they only stress how important it is to standardize interoperability APIs like context-propagation and data specification.</p><h3>2016: OpenTracing</h3><p>OpenTracing was created at the end of 2015 and it started gaining significant traction at the end of 2016 as it <a href="https://www.cncf.io/blog/2016/10/11/opentracing-joins-the-cloud-native-computing-foundation/">joined Cloud Native Computing Foundation</a>. The promise was great: a vendor neutral instrumentation API and specification that defines data collection. Clearly a huge win on paper because instrumentation is the hardest part when deploying a distributed tracing system. The project also included a contrib organization with instrumentation libraries for many frameworks.</p><p>One of the main goals of the project was to incorporate OpenTracing API directly into RPC frameworks. This again sounds great but it didn’t pan out how we wanted. The RPC framework authors are very sensitive to using 3rd party dependencies. Some frameworks (e.g. Vertx) decided to create their own tracing API and only provide an OpenTracing bridge. The other significant issue was that application authors (e.g. Envoy) didn’t know which OpenTracing implementation should be included in the final compiled distribution. Including multiple implementations created unnecessary maintenance overhead and it complicated the final distribution.</p><p>Despite many issues, OpenTracing achieved great things and moved OSS distributed tracing forward into a better place. It paved the way for OpenTelemetry to be even more successful. Personally, I would like to summarize the outcome of the project.</p><p>Pros:</p><ul><li>specification for data collection</li><li>it helped to promote distributed tracing</li></ul><p>Cons:</p><ul><li>API only, no standard implementation. It was a huge confusion for end-users and duplicated effort for vendors to create implementations.</li><li>when switching the implementation need to recompile and redeploy applications</li><li>no reporting format definition</li><li>no context-propagation format</li></ul><h3>2017: Open-sourced Jaeger</h3><p>In early 2017 <a href="https://eng.uber.com/yuri-shkuro-interview/">Uber engineering open-sourced Jaeger</a>. The first version was very similar to Zipkin and with a couple of key differences:</p><ul><li>written in Golang</li><li>native support for full OpenTracing API. For instance, Zipkin for a long time didn’t support span logs</li><li>first-class support for OpenTracing implementations in multiple languages. All the clients were hosted directly in the Jaeger organization and properly tested.</li><li>support for endpoint-based sampling and remotely controlled sampling</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*TZFCOYlT7DPzbN3p" /></figure><p>Over the years the Jaeger project evolved and many features were added. We have always made sure the tool is super easy to use, no matter if directly as a compiled binary or deployed on Kubernetes. When building an OSS ecosystem it’s important to not to say no and rather find a generic solution that works for everybody.</p><p>When looking to the future, Jaeger will embrace OpenTelemetry and the V2 version will be based on the OpenTelemetry collector. I would like to also highlight that the Jaeger project expands with new “APM” capabilities like <a href="https://github.com/jaegertracing/jaeger/issues/2954">metrics support</a>.</p><h3>2019: OpenTelemetry</h3><p>The <a href="https://medium.com/jaegertracing/jaeger-and-opentelemetry-1846f701d9f2">OpenTelemetry project was announced in 2019</a> as a merger of OpenTracing and OpenCensus projects. The project promised to combine the best from both of these projects:</p><ul><li>API and official standard implementation (SDK)</li><li>data collection specification</li><li>mandatory support for <a href="https://www.w3.org/TR/trace-context/">W3C-Trace Context</a></li><li>coming support for metrics and logs</li><li>standardized data model (OTLP)</li><li>a local collector that accepts the most popular tracing formats (OTLP, Zipkin, Jaeger) and exports data to vendor systems</li><li>auto instrumentation technology for various languages</li></ul><p>The features mentioned above make it clear that OpenTelemetry promises to deliver robust end-to-end data collection solution. The major differentiator over OpenTracing is the standardized data model, official SDKs and collector. All these combined means that users do not have to touch their workloads when changing observability vendors. This is a huge win as it avoids vendor lock-in!</p><p>I am pretty confident that OpenTelemetry will be a successful project and it will take OSS distributed tracing to the next level. Note that the project is <a href="https://twitter.com/opentelemetry/status/1262468392004382721">the second most active CNCF project</a> after Kubernetes. A lot of active members and vendors has its toll as well. The project velocity is impacted and getting things done takes more time.</p><h3>2020/2021: Hypertrace, Grafana Tempo and SigNoz</h3><p>The year 2020 and 2021 was a really interesting one. In 2020 the API observability project <a href="https://www.hypertrace.org/">Hypertrace</a> was open-sourced. The major differentiator to Jaeger is that it makes APIs and databases/backends first-class entities in the system and provides a powerful slice and dice explorer. For instance, a user can only by a few clicks see the performance of database calls or 3rd party service. The project also includes OpenTelemetry compatible agents that extend the upstream project by capturing 100% of traffic (e.g. payloads and headers). The clients or agents have a pluggable filter mechanism that allows users to run arbitrary code to analyze requests, which is pretty cool! The clients are licensed under Apache 2.0 and the server has a hybrid license model with Apache 2.0 and proprietary Traceable Community License 1.0.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*TlzauOkrSJNfsUA6" /></figure><p>In 2021 a similar tool called <a href="https://signoz.io/">SigNoz</a> was born. Similarly to Hypertrace, its goal is to be a full observability system with metrics, logs and traces. The platform also provides rich aggregates on filtered traces which is possible due to the OLAP database. The project uses OSS Apache 2 license.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*YqSntEDABeeKquZM" /></figure><p>Another important project that was open-sourced in 2020 is Grafana Tempo. Although it is not a full observability system it brings nice innovation to the ecosystem. It’s a local trace data store highly inspired by Prometheus. Its biggest downside is the query capabilities. It only allows returning trace by ID.</p><h3>Conclusion and peek into the {bright} future</h3><p>As we have seen open-source tracing projects made a big step forward since 2016. At the beginning Zipkin was the only major OSS tracing system that developers could use. It definitely paved the way and had a great design like a stable data model and context propagation.</p><p>Now, developers have a choice to choose from multiple OSS tracing systems, use robust libraries to collect data from their applications by using OpenTelemetry and use standardized context propagation W3C Trace-Context.</p><p>So what is next? I wish I had the answer, but I can certainly express what I think. The OpenTelemetry project will be around and it will become more robust and provide well-tested instrumentation libraries to collect various signals including metrics, logs and traces. We will also see more instrumentation for capturing profiling data. The tracing systems will be adding more APM-like capabilities — e.g. better metrics support and profiling. The cloud vendors will integrate with OpenTelemetry and will embed instrumentation capabilities directly in their platforms. Last but not least we will see runtime security projects being integrated into observability.</p><h3>References</h3><ul><li>Hawkular APM: <a href="https://hawkular.gitbooks.io/hawkular-apm-user-guide/content/">https://hawkular.gitbooks.io/hawkular-apm-user-guide/content/</a></li><li>Jaeger Tracing: <a href="https://www.jaegertracing.io/">https://www.jaegertracing.io/</a></li><li>Zipkin: <a href="https://zipkin.io/">https://zipkin.io/</a></li><li>OpenTelemetry: <a href="https://opentelemetry.io/">https://opentelemetry.io/</a></li><li>OpenCensus: <a href="https://opencensus.io/">https://opencensus.io/</a></li><li>Hypertrace: <a href="https://www.hypertrace.org/">https://www.hypertrace.org/</a></li><li>SigNoz: <a href="https://signoz.io/">https://signoz.io/</a></li><li>Apache SkyWalking: <a href="https://skywalking.apache.org/">https://skywalking.apache.org/</a></li><li>W3C Trace-Context: <a href="https://www.w3.org/TR/trace-context/">https://www.w3.org/TR/trace-context/</a></li><li>Grafana Tempo: <a href="https://github.com/grafana/tempo">https://github.com/grafana/tempo</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ec1c5a5dd1ac" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Jaeger clients and W3C Trace-Context]]></title>
            <link>https://medium.com/jaegertracing/jaeger-clients-and-w3c-trace-context-c2ce1b9dc390?source=rss-a20a4172f9c------2</link>
            <guid isPermaLink="false">https://medium.com/p/c2ce1b9dc390</guid>
            <category><![CDATA[opentelemetry]]></category>
            <category><![CDATA[opentracing]]></category>
            <category><![CDATA[jaegertracing]]></category>
            <dc:creator><![CDATA[Pavol Loffay]]></dc:creator>
            <pubDate>Tue, 26 May 2020 14:02:02 GMT</pubDate>
            <atom:updated>2020-05-27T16:00:32.829Z</atom:updated>
            <content:encoded><![CDATA[<h3>Using Jaeger and OpenTelemetry SDKs in a mixed environment with W3C Trace-Context</h3><p>In this article, we are going to have a look at using Jaeger clients with W3C Trace-Context propagation format. The standardized context propagation format assures interoperability between different tracing systems and instrumentation libraries. In this regard we are going to explore two use cases. First how to use OpenTelemetry SDKs in Jaeger instrumented environment. This scenario simulates interoperability between different tracing systems but also a migration path from Jaeger clients to OpenTelemetry SDK. In the second use case we are going to configure Jaeger native clients to use W3C Trace-Context.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*74Ds73EYN-uqliyrxp1OKQ.png" /></figure><h3>W3C Trace-Context</h3><p>Before we deep dive into the main topic let’s have a look at <a href="https://www.w3.org/TR/trace-context/">W3C Trace-Context</a>. W3C Trace-Context is a standardized propagation format for trace identifiers and metadata. It contains two headers:</p><ul><li>traceparent — describes the position of the incoming request in its trace graph. It encodes trace ID, parent span ID and set of flags.</li><li>tracestate — extends traceparent with vendor-specific data represented by a set of name/value pairs. This header is optional and can, for instance, encode tenant name.</li></ul><p>The standardized inter-process context propagation format solves these problems:</p><ul><li>exchanging trace context between different vendors</li><li>correlation of traces collected by different tracing vendors</li><li>vendor-specific metadata might be dropped by intermediaries, cloud providers and service providers if there is no standard to follow.</li></ul><p>Now let’s have a look at OpenTelemetry Java SDK configuration in a mixed environment with Jaeger native clients.</p><h3>Configure OpenTelemetry SDK with Jaeger context propagation format</h3><p>In this section, we are going to have a look at the configuration of OpenTelemetry Java SDK in an environment that is instrumented with Jaeger native clients. This use case is important when we want to introduce new services instrumented with OpenTelemetry and it also showcases interoperability between different tracing instrumentations. In a mixed environment, we have to make sure that all instrumentation libraries understand the same <a href="https://opentracing.io/docs/overview/inject-extract/">propagation protocol</a>.</p><p>Since existing services cannot be recompiled and redeployed to use the new context propagation format, the new services have to be configured to understand the new and old propagation formats at the same time. Follows a simple diagram showing three microservices A, B and X. Services A and B are instrumented with Jaeger and X with OpenTelemetry. Only the service X is using both propagation protocols — Jaeger and W3C Trace-Context. This ensures that “legacy” Jaeger services are able to continue the trace started by service X and vice versa.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/960/1*q47-wlfwAppyUXMot6H2bQ.png" /><figcaption>Diagram showing service X instrumented with OpenTelemetry and services A and B instrumented with Jaeger.</figcaption></figure><p>Now let’s have a look at the code. We are going to configure OpenTelemetry Java SDK to use the default W3C Trace-Context and Jaeger formats simultaneously. First, let’s add OpenTelemetry maven artifacts for SDK and Jaeger context propagation format:</p><pre>&lt;dependency&gt;<br> &lt;groupId&gt;io.opentelemetry&lt;/groupId&gt;<br> &lt;artifactId&gt;opentelemetry-sdk&lt;/artifactId&gt;<br> &lt;version&gt;0.4.1&lt;/version&gt;<br>&lt;/dependency&gt;<br>&lt;dependency&gt;<br> &lt;groupId&gt;io.opentelemetry&lt;/groupId&gt;<br> &lt;artifactId&gt;opentelemetry-contrib-trace-propagators&lt;/artifactId&gt;<br> &lt;version&gt;0.4.1&lt;/version&gt;<br>&lt;/dependency&gt;</pre><p>and the tracer configuration:</p><pre>import io.opentelemetry.OpenTelemetry;<br>import io.opentelemetry.trace.Span;<br>import io.opentelemetry.trace.Tracer;<br>import io.opentelemetry.contrib.trace.propagation.JaegerPropagator; import io.opentelemetry.trace.propagation.HttpTraceContext;<br>import io.opentelemetry.context.propagation.ContextPropagators;<br>import io.opentelemetry.context.propagation<br>         .DefaultContextPropagators;</pre><pre>ContextPropagators contextPropagators = DefaultContextPropagators.<em>builder</em>()<br>    .addHttpTextFormat(new JaegerPropagator())<br>    .addHttpTextFormat(new HttpTraceContext())<br>    .build();<br>OpenTelemetry.<em>setPropagators</em>(contextPropagators);<br><br>Tracer tracer = OpenTelemetry.<em>getTracerProvider</em>().get(&quot;io.example&quot;);<br>Span span = tracer.spanBuilder(&quot;operation&quot;).startSpan();<br>...</pre><p>Now the tracer is configured to use both W3C Trace-Context and Jaeger propagation formats simultaneously for <a href="https://opentracing.io/docs/overview/inject-extract/">context injection and extraction</a>. The legacy Jaeger instrumentations will recognize the Jaeger propagation format and drop the W3C Trace-Context.</p><h3>Configure Jaeger clients with W3C Trace-Context</h3><p>Jaeger native clients can be configured to use custom context propagation formats. Support for W3C Trace-Context is usually provided directly in the core artifacts. It can be configured programmatically or via an <a href="https://www.jaegertracing.io/docs/latest/client-features/">environment variable</a> JAEGER_PROPAGATION=w3c.</p><p>The following code snippet shows the programmatic configuration that configures both Jaeger and W3C Trace-Context formats at the same time.</p><pre>import io.jaegertracing.Configuration;<br>import io.jaegertracing.Configuration.Propagation;</pre><pre>Configuration configuration = Configuration.<em>fromEnv</em>(&quot;io.example&quot;);<br>configuration.getCodec()<br>    .withPropagation(Propagation.<em>W3C</em>)<br>    .withPropagation(Propagation.<em>JAEGER</em>);<br>JaegerTracer jaegerTracerWith = configuration.getTracer();</pre><h3>Conclusion</h3><p>In this article we have shown how OpenTelemetry SDKs can be used in a heterogeneous environment with Jaeger native clients and also how W3C Trace-Context can be configured in Jaeger clients.</p><h3>References</h3><ul><li>W3C Trace-Context: <a href="https://www.w3.org/TR/trace-context/">https://www.w3.org/TR/trace-context/</a></li><li>Jaeger clients documentation: <a href="https://www.jaegertracing.io/docs/latest/client-features/">https://www.jaegertracing.io/docs/latest/client-features/</a></li><li>OpenTelemetry documentation: <a href="https://opentelemetry.io/">https://opentelemetry.io/</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c2ce1b9dc390" width="1" height="1" alt=""><hr><p><a href="https://medium.com/jaegertracing/jaeger-clients-and-w3c-trace-context-c2ce1b9dc390">Jaeger clients and W3C Trace-Context</a> was originally published in <a href="https://medium.com/jaegertracing">JaegerTracing</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Jaeger embraces OpenTelemetry collector]]></title>
            <link>https://medium.com/jaegertracing/jaeger-embraces-opentelemetry-collector-90a545cbc24?source=rss-a20a4172f9c------2</link>
            <guid isPermaLink="false">https://medium.com/p/90a545cbc24</guid>
            <category><![CDATA[distributed-tracing]]></category>
            <category><![CDATA[opentracing]]></category>
            <category><![CDATA[opentelemetry]]></category>
            <category><![CDATA[jaegertracing]]></category>
            <dc:creator><![CDATA[Pavol Loffay]]></dc:creator>
            <pubDate>Fri, 01 May 2020 08:30:02 GMT</pubDate>
            <atom:updated>2020-06-03T10:11:50.300Z</atom:updated>
            <content:encoded><![CDATA[<p>In this article we are going to explain Jaeger integration with the OpenTelemetry collector, describe differences between these two and have a look at Kubernetes deployment via Jaeger Operator. See <a href="https://medium.com/jaegertracing/jaeger-and-opentelemetry-1846f701d9f2">Jaeger and OpenTelemetry</a> post by <a href="https://medium.com/u/cff2e4ba6058">Yuri Shkuro</a> on the long-term integration roadmap.</p><p><a href="https://github.com/open-telemetry/opentelemetry-collector">OpenTelemetry collector</a> is a vendor-agnostic service for receiving, processing and exporting telemetry data. In the Jaeger project we have decided to deprecate the Jaeger collector and migrate its functionality to an implementation based on OpenTelemetry collector. This has several benefits:</p><ul><li>forward compatibility with OpenTelemetry native data model</li><li>tail-based sampling</li><li>attribute processors</li><li><a href="https://opentelemetry.io/">standardized collection pipeline</a></li><li>less code to maintain</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*74Ds73EYN-uqliyrxp1OKQ.png" /></figure><h3>Roadmap</h3><p>In the long term we would like to base the Jaeger collector, agent and ingester components on OpenTelemetry collector. These new components will be separate distributions with new image and binary names.</p><p>Our goal is to provide a smooth migration from existing binaries by supporting legacy <a href="https://www.jaegertracing.io/docs/latest/cli/">Jaeger configuration</a> (flags, env. vars, Jaeger configuration file). However, there will be a couple of breaking changes:</p><ul><li>a different set of metrics exposed by these components</li><li>different health check and metric endpoints</li><li>not all flags will be supported in the new component e.g. --metrics-backed, --collector.queue-size</li></ul><p>Follow <a href="https://github.com/jaegertracing/jaeger/milestone/12">this milestone on Github</a> to see what is missing for the first stable release.</p><p>In the meantime, you can use collector image with thelatest tag jaegertracing/jaeger-opentelemetry-collector:latest.</p><h3>Configuration &amp; give it a try!</h3><p>Upstream OpenTelemetry collector is configurable via a configuration file that is provided as a flag at application startup--config-file=config.yaml. If the configuration file is missing the collector will not start because it does not know what components (receivers, processors, exporters) should be used.</p><p>Jaeger’s build of the OpenTelemetry collector is opinionated about the configuration and it always uses a set of default components: Jaeger receiver, <a href="https://github.com/jaegertracing/jaeger/issues/2152">processors</a>, and exporter. The exporter is one of Jaeger’s supported storage backends: Elasticsearch, Cassandra, Kafka (buffer). The configuration provided in the file is merged with the default configuration.</p><p>Jaeger specific components can be configured by the same <a href="https://www.jaegertracing.io/docs/latest/cli/">flags</a> that were exposed by the Jaeger collector e.g. --es.server-urls. The configuration provided in OpenTelemetry config has higher precedence.</p><p>The configuration precedence from the lowest to the highest is as follows:</p><ol><li>Jaeger <a href="https://www.jaegertracing.io/docs/latest/cli/">default values</a> &lt;Jaeger config file&lt; environmental variables &lt; flags (<a href="https://github.com/spf13/viper#why-viper">Viper’s default precedence order</a>)</li><li>OpenTelemetry configuration file</li></ol><p>Let’s have a look at an example that configures Jaeger OpenTelemetry collector:</p><ul><li>Enables Elasticsearch backend with URL <a href="http://elasticsearch:9200">http://elasticsearch:9200</a>, 3 primary shards (default is 5) and 2 replica shards (default 1)</li><li>Disables batch processor (enabled by default). It’s disabled because it is not specified in the service.pipelines.traces.processors array.</li><li>Enables attribute processor (disabled by default). Note that new components have to be explicitly added to the pipeline.</li></ul><pre>docker run --rm -it -v ${PWD}:/config \<br>    -e SPAN_STORAGE_TYPE=elasticsearch \<br>    jaegertracing/jaeger-opentelemetry-collector \<br>    --config-file=/config/config.yaml \<br>    --es.server.urls=<a href="http://localhost:9200">http://localhost:9200</a> \<br>    --es.num-shards=3</pre><p>The content of config.yaml:</p><pre>exporters:<br>  jaeger_elasticsearch:<br>    es:<br>      server-urls: <a href="http://elastic:9200">http://elasticsearch:9200</a><br>      num-replicas: 2</pre><pre>processors:<br>  attributes:<br>    actions:<br>      - key: user<br>        action: delete</pre><pre>service:<br>  pipelines:<br>    traces: <br>      processors: [attributes]</pre><p>The storage exporter can be configured via the same environmental variable SPAN_STORAGE_TYPE as Jaeger collector or it can be specified in service.pipelines.traces.exporters.</p><h3>Jaeger Operator</h3><p>Because the new components can be considered as almost drop-in replacements for existing Jaeger binaries we will be able to directly use them in the Jaeger Operator, by explicitly providing the image name in the CR. The required change in the Jaeger Operator is to expose the OpenTelemetry configuration in the CR. At the moment this is just a design proposal that is being discussed in the issue <a href="https://github.com/jaegertracing/jaeger-operator/issues/1004">jaeger-operator/issues/1004</a>.</p><p>Following the same configuration as in the previous section, the OpenTelemetry configuration is directly embedded into Jaeger collector node:</p><pre>apiVersion: jaegertracing.io/v1<br>kind: Jaeger<br>metadata:<br>  name: simple-prod<br>spec:<br>  strategy: production<br>  collector:<br>    config: |<br>      exporters:<br>        jaeger_elasticsearch:<br>          es:<br>            server-urls: http://elasticsearch:9200<br>            num-replicas: 2<br>      processors:<br>        attributes:<br>          actions:<br>            - key: user<br>              action: delete<br>      service:<br>        pipelines:<br>          traces:<br>            processors: [attributes]<br>  storage:<br>    type: elasticsearch<br>    options:<br>      es:<br>        server-urls: http://localhost:9200<br>        num-shards: 3</pre><h3>Conclusion</h3><p>We have explained how the Jaeger project integrates with OpenTelemetry collector and what are the key differences between these components. Share your feedback with us and try our new collector based on OpenTelemetry.</p><h3>References</h3><ul><li>Jaeger OpenTelemetry collector source code: <a href="https://github.com/jaegertracing/jaeger/tree/master/cmd/opentelemetry-collector">https://github.com/jaegertracing/jaeger/tree/master/cmd/opentelemetry-collector</a></li><li>Jaeger OpenTelemetry collector docker image: <a href="https://hub.docker.com/r/jaegertracing/jaeger-opentelemetry-collector">https://hub.docker.com/r/jaegertracing/jaeger-opentelemetry-collector</a></li><li>OpenTelemetry collector: <a href="https://github.com/open-telemetry/opentelemetry-collector">https://github.com/open-telemetry/opentelemetry-collector</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=90a545cbc24" width="1" height="1" alt=""><hr><p><a href="https://medium.com/jaegertracing/jaeger-embraces-opentelemetry-collector-90a545cbc24">Jaeger embraces OpenTelemetry collector</a> was originally published in <a href="https://medium.com/jaegertracing">JaegerTracing</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Jaeger data analytics with Jupyter notebooks]]></title>
            <link>https://medium.com/jaegertracing/jaeger-data-analytics-with-jupyter-notebooks-b094fa7ab769?source=rss-a20a4172f9c------2</link>
            <guid isPermaLink="false">https://medium.com/p/b094fa7ab769</guid>
            <category><![CDATA[opentracing]]></category>
            <category><![CDATA[opentelemetry]]></category>
            <category><![CDATA[jaegertracing]]></category>
            <dc:creator><![CDATA[Pavol Loffay]]></dc:creator>
            <pubDate>Tue, 17 Mar 2020 12:01:02 GMT</pubDate>
            <atom:updated>2020-03-17T12:01:02.068Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/348/1*sCIKhVlBwgG1XTdSbaVFAA.png" /></figure><p>In the previous blog post <a href="https://medium.com/jaegertracing/data-analytics-with-jaeger-aka-traces-tell-us-more-973669e6f848">Data analytics with Jaeger aka traces tell us more!</a> we have introduced our data science initiative and platform. The ultimate goal is to develop new functionality within the Jaeger project based on AI/ML that will provide new insights into our applications. This type of functionality is also referred to as AI operations (AIOps).</p><h4>Jupyter notebooks</h4><p>Jupyter notebooks provide a simple user interface for experimenting with data. There are two main use cases that we want to accomplish with the notebooks:</p><ul><li>an interface for data scientists to experiment with tracing data</li><li>on-demand incident investigation</li></ul><p>The first use case is self-explanatory. It lowers the bar for non-infrastructure people to connect to the Jaeger server to consume and analyze the data. The second use case is more tricky. Imagine an incident that requires us to analyze a trace or set of traces with hundreds or thousands of spans. Such an analysis might not be feasible in the user interface. Instead, we could write code to verify our hypothesis. For this purpose we have developed <a href="https://medium.com/jaegertracing/data-analytics-with-jaeger-aka-traces-tell-us-more-973669e6f848">Trace DSL</a> based on graph query language Gremlin to simplify filtering and feature extraction of tracing data. Hence a Jaeger user would be able to spin up a Jupyter notebook on demand with the Trace DSL and write a query and analysis.</p><h3>Jupyter notebook example with Jaeger</h3><p>In this section we are going to deploy Jupyter notebook with Jaeger Trace DSL and write a simple query against the Jaeger server.</p><p>Let’s deploy Jaeger, HotROD example and Jupyter notebook with Trace DSL:</p><pre>docker run --rm -it -p 16686:16686 --name=jaeger jaegertracing/all-in-one:1.17</pre><pre>docker run --rm -it -p 8080:8080 --link=jaeger -e JAEGER_ENDPOINT=<a href="http://jaeger:14268/api/traces">http://jaeger:14268/api/traces</a> jaegertracing/example-hotrod:1.17<br></pre><pre>docker run --rm -it -p 8888:8888 -p 4041:4040 -p 9001:9001 --link=jaeger -e JUPYTER_ENABLE_LAB=yes quay.io/jaegertracing/jaeger-analytics-java:latest</pre><p>Add -v ${PWD}:/home/jovyan/work to Jupyter notebook if you want to open the notebooks from your current directory. The notebooks are hosted in <a href="https://github.com/jaegertracing/jaeger-analytics-java">jaegertracing/jaeger-analytics-java</a> repository.</p><p>Now open Jaeger UI at <a href="http://localhost:16686">http://localhost:16686</a>, HotROD example at <a href="http://localhost:8080">http://localhost:8080</a> and Jupyter notebook at <a href="http://localhost:8888/lab">http://localhost:8888/lab</a>. The token for the Jupyter lab is written in the Jupyter console logs.</p><p>For the analysis we have to generate some data, so in the <a href="http://localhost:8080">HotROD UI</a> click on the blue boxes to order a car that generates nice traces. To verify that the trace reached Jaeger open <a href="http://localhost:16686">Jaeger UI</a> and search for traces from thefrontend service. The trace should look like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6g2yGFzfx5GT9-RKdKD8Rw.png" /><figcaption>Trace from the HotROD example application.</figcaption></figure><p>Once we know that the data is stored in Jaeger we can move to Jupyter notebook and load the trace there. Jaeger notebooks are stored in thejupyter directory. This directory can either be opened from the project root directory or from work in case the notebooks from host filesystem are injected into the docker container.</p><p>Before running the analysis we have to load dependencies into the notebook’s classpath. Just click on the dependencies cell to make it active and then on the play icon in the top navigation menu.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YgoMF6gn7CWjARJFA7TCpw.png" /><figcaption>Jupyter notebook for loading data from jaeger-query.</figcaption></figure><p>Before running the code we have to update the variable traceIdStr to point into one of the traces we have generated earlier in the HotROD app.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gVpY0qIuGJlTfIB6rFU-Dg.png" /><figcaption>Jupyter notebook for loading data from jaeger-query.</figcaption></figure><p>The results are written below the code cell. In this case the trace has the height 3 and there is one calculated network latency between frontend and server service of 0.00102 ms . The latency is small because all services are run as part of the same process and there is no real network overhead.</p><p>The last code cell shows the direct use of Trace DSL with <a href="http://tinkerpop.apache.org/docs/current/reference/">Apache Gremlin</a>. Gremlin is a graph traversal language and class TraceTraversalSource.class it extends and adds methods for trace filtering and feature extraction. For instance, it adds a method like hasName(String name) to filter spans by operation name. In our example the query verifies whether two spans with given operation names are directly or indirectly connected, or in other words one is a descendant of the other.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ioN_wFSUw3bCAVLEW_oTJw.png" /><figcaption>Jupyter notebook for loading data from jaeger-query.</figcaption></figure><h3>Conclusion</h3><p>We have seen how easy it is to deploy Jupyter notebook and write a simple query against Jaeger server using gRPC generated stubs. This feature provides a powerful interface to write a custom query and hypothesis analysis against traces retrieved from the Jaeger server. The example can easily be extended to collect a stream of traces from Kafka and experiment on the live data.</p><p>Any feedback is welcome! Get in touch with us on our <a href="https://gitter.im/jaegertracing/Lobby">Jaeger Gitter</a> channel or simply <a href="https://github.com/jaegertracing/jaeger-analytics-java">open an issue</a> and share your feedback or ideas.</p><h3>References</h3><ul><li>Jaeger Java analytics: <a href="https://github.com/jaegertracing/jaeger-analytics-java">https://github.com/jaegertracing/jaeger-analytics-java</a></li><li>Data analytics with Jaeger blog post: <a href="https://medium.com/jaegertracing/data-analytics-with-jaeger-aka-traces-tell-us-more-973669e6f848">https://medium.com/jaegertracing/data-analytics-with-jaeger-aka-traces-tell-us-more-973669e6f848</a></li><li>Apache Gremlin documentation: <a href="http://tinkerpop.apache.org/docs/current/reference/">http://tinkerpop.apache.org/docs/current/reference/</a></li><li>Jaeger HotROD example application demo <a href="https://medium.com/opentracing/take-opentracing-for-a-hotrod-ride-f6e3141f7941">https://medium.com/opentracing/take-opentracing-for-a-hotrod-ride-f6e3141f7941</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b094fa7ab769" width="1" height="1" alt=""><hr><p><a href="https://medium.com/jaegertracing/jaeger-data-analytics-with-jupyter-notebooks-b094fa7ab769">Jaeger data analytics with Jupyter notebooks</a> was originally published in <a href="https://medium.com/jaegertracing">JaegerTracing</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[T-Trace: agent-like instrumentation for GraalVM]]></title>
            <link>https://medium.com/jaegertracing/t-trace-agent-like-instrumentation-for-graalvm-d8b79b755e71?source=rss-a20a4172f9c------2</link>
            <guid isPermaLink="false">https://medium.com/p/d8b79b755e71</guid>
            <category><![CDATA[opentracing]]></category>
            <category><![CDATA[instrumentation]]></category>
            <category><![CDATA[jaegertracing]]></category>
            <dc:creator><![CDATA[Pavol Loffay]]></dc:creator>
            <pubDate>Mon, 24 Feb 2020 11:51:01 GMT</pubDate>
            <atom:updated>2020-02-27T11:34:24.299Z</atom:updated>
            <content:encoded><![CDATA[<p>In this article we will look at the agent-like instrumentation tool <a href="https://github.com/oracle/graal/blob/master/tools/docs/T-Trace.md">T-Trace</a>. The tool provides non-intrusive instrumentation capabilities for applications running on <a href="https://www.graalvm.org/">GraalVM</a>. We will instrument a simple NodeJS application by using T-Trace and OpenTracing API with Jaeger NodeJS tracer.</p><p>So first things first, GraalVM is a polyglot virtual machine. It can run any JVM based language but also other mainstream languages like NodeJS, Python and Ruby… It also supports LLVM languages like C and C++. The sweet spot is that the languages can be combined together in a single application. Another very interesting feature is that GraalVM can produce a native executable for JVM languages. This feature is used by modern cloud-native Java runtimes like <a href="https://quarkus.io/">Quarkus</a> and <a href="https://micronaut.io/">Micronaut</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/400/1*aQ9FuQqtAsx1ki8scvEuHg.jpeg" /></figure><h3>Styles of instrumentation</h3><p>Before we deep dive into the demo application let’s have a look at different instrumentation techniques. There are several instrumentation styles that can be applied to code fragments or the whole application. At a high level we can talk about black-box and white-box instrumentation. The black-box technique does not require any modifications to the application. The monitoring system, in this case, uses only events produced by the application. These events can be logs or any data that the application produces.</p><p>In this article we are going to focus on the white-box technique. This technique assumes that the code of the application is known and can be inspected. There are several methods of how the instrumentation points can be added to the application:</p><ul><li>Non-intrusive agent-like instrumentation: in this case the instrumentation is added to the application dynamically. In some languages (Python, Ruby, JavaScript) this technique is called <a href="https://en.wikipedia.org/wiki/Monkey_patch">monkey-patching</a> where modules or classes are dynamically replaced at runtime. In other languages less dynamic techniques are allowed, for instance -javaagent in Java.</li><li>Explicit instrumentation: this instrumentation is applied directly to the code. However it does not mean that the application developer has to instrument all the code. The instrumentation code can be embedded directly into (RPC) frameworks or external integration. In some languages and runtime frameworks it is easier to initialize the instrumentation like in other languages. For instance in Java it’s enough just to drop an instrumentation JAR into the classpath, whereas in statically linked languages like Golang the initialization has to be done explicitly.</li></ul><h3>The application</h3><p>The application is a simple NodeJS server with one handler. The full demo code with instructions can be found in <a href="https://github.com/pavolloffay/graalvm-t-trace">pavolloffay/graalvm-t-trace</a>. Here is the server.js file:</p><pre>const port = 3000;<br><br>const http = require(&quot;http&quot;);<br>const srv = http.createServer((req, res) =&gt; {<br>  <strong><em>console</em></strong>.log(`server: obtained request ${res.id}`);<br>  setTimeout(() =&gt; {<br>    res.write(`OK# ${res.id}`);<br>    <strong><em>console</em></strong>.log(`server: replied to request ${res.id}`);<br>    res.end();<br>  }, 5);<br>});<br>srv.listen(port);</pre><p>As you can see there is no tracing related in the application. The application prints request ids at the beginning and at the end of the request. The res.id is set in the agent script — this is just for the demo purpose to demonstrate that the agent script can pass objects to the main application.</p><p>Now let’s have a look at the instrumentation script jaegernode.js. There are two functions: initializeJaeger and initializeAgent. The first function creates Jaeger tracer instance with HTTP sender to the collector port 14268 and calls the second one at the end:</p><pre>let initializeJaeger = function (require) {<br>    let http = require(&quot;http&quot;);    console.log(`${typeof     <br>    http.createServer} http.createServer is available to the agent`);     <br>    var initTracer = require(&#39;jaeger-client&#39;).initTracer;       <br>    console.log(&#39;server: Jaeger tracer obtained&#39;);</pre><pre>// See schema https://github.com/jaegertracing/jaeger-client-node/blob/master/src/configuration.js#L37<br>    var config = {<br>        serviceName: &#39;t-trace-demo&#39;,<br>        reporter: {<br>            // Provide the traces endpoint; this forces the client to connect directly to the Collector and send<br>            // spans over HTTP<br>            collectorEndpoint: &#39;http://localhost:14268/api/traces&#39;,<br>            // logSpans: true<br>        },<br>        sampler: {<br>            type : &#39;const&#39;,<br>            param : 1<br>        }<br>    };<br>    var options = {<br>        tags: {<br>            &#39;t-trace-demo.version&#39;: &#39;1.1.2&#39;,<br>        },<br>        //  metrics: metrics,<br>        logger: <strong><em>console</em></strong>,<br>        sampler: {<br>            type : &#39;const&#39;,<br>            param : 1<br>        }<br>    };<br><br>    var tracer = initTracer(config, options);<br>    initializeAgent(tracer);<br>};<br><br>let waitForRequire = function (event) {<br>    if (typeof process === &#39;object&#39; &amp;&amp; process.mainModule &amp;&amp;  <br>                 process.mainModule.require) {<br>        agent.off(&#39;source&#39;, waitForRequire); <br>        initializeJaeger(  <br>             process.mainModule.require.bind(process.mainModule));<br>    }<br>};<br><br>agent.on(&#39;source&#39;, waitForRequire, { roots: true });</pre><p>The second function is initializeAgent which is instrumenting the application code:</p><pre>let initializeAgent = function(tracer) {<br>    agent.on(&#39;enter&#39;, function(ctx, frame) {<br>        const args = frame.args;<br>        if (&#39;request&#39; !== frame.type || args.length !== 2 || typeof args[0] !== &#39;object&#39; || typeof args[1] !== &#39;object&#39;) {<br>            return;<br>        }<br>        const req = args[0];<br>        const res = args[1];<br>        const span = tracer.startSpan(req.method);<br>        span.setTag(&quot;span.kind&quot;, &quot;server&quot;);<br>        span.setTag(&quot;http.url&quot;, req.url);<br>        span.setTag(&quot;http.method&quot;, req.method);<br>        res.id = span.context().spanIdStr;<br>        res.span = span;<br>        <strong><em>console</em></strong>.log(`agent: handling #${res.id} request for ${req.url}`);<br>    }, {<br>        roots: true,<br>        rootNameFilter: name =&gt; name === &#39;emit&#39;,<br>        sourceFilter: src =&gt; src.name === &#39;events.js&#39;<br>    });<br><br>    agent.on(&#39;return&#39;, function(ctx, frame) {<br>        var res = frame[&#39;this&#39;];<br>        if (res.span) {<br>            res.span.setTag(&quot;http.status_code&quot;, res.statusCode);<br>            if (res.statusCode &gt;= 400) {<br>                res.span.setTag(&quot;error&quot;, &quot;true&quot;);<br>            }<br>            res.span.finish();<br>            <strong><em>console</em></strong>.log(`agent: finished #${res.id} request`);<br>        } else {<br>            // OK, caused for example by Tracer itself connecting to Jaeger server<br>        }<br>    }, {<br>        roots: true,<br>        rootNameFilter: name =&gt; name === &#39;end&#39;,<br>        sourceFilter: src =&gt; src.name === &#39;_http_outgoing.js&#39;<br>    });<br>    <strong><em>console</em></strong>.log(&#39;agent: ready&#39;);<br>};</pre><p>The instrumentation is done via agent.on(&#39;enter&#39;, fn) and agent.on(&#39;return&#39;, fn). The first instrumentation point is invoked when any function from the application is invoked and the second when a function returns execution the caller. The agent.on function has access to the frame variables and also to the method arguments. The arguments are used in a condition to check whether the function is HTTP handler. You can also note that the span object is injected into the response.</p><p>Now let’s run the application and Jaeger server:</p><pre>docker run --rm -it --net=host jaegertracing/all-in-one:1.16.0<br>$GRAALVM_HOME/bin/npm install jaeger-client@3.17.2<br>$GRAALVM_HOME/bin/node --experimental-options --js.print=true  --agentscript=jaegernode.js server.js<br>curl http://localhost:3000</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BGyy3CFk70DjKRGmJzZf0g.png" /><figcaption>Screenshot from Jaeger showing trace from t-trace-demo application.</figcaption></figure><h3>Conclusion</h3><p>We have seen a NodeJS hello-world example of T-Trace with Jaeger and OpenTracing. The demo shows how agent-like instrumentation can be applied to the NodeJS application without <em>monkey-patching</em>.</p><p>There are many improvements that can be made to the code. For instance we could extend it by not instrumenting the code in the agent script but rather reusing OpenTracing <a href="https://github.com/opentracing-contrib">instrumentation</a> for NodeJS and just install it in the agent script. Another possible improvement we could do is to support <a href="https://github.com/oracle/graal/blob/master/tools/docs/T-Trace-Embedding.md">dynamic</a> loading of the script. In this case the application will be started with the agent script that exposes REST API for loading and disabling scripts. This is a very powerful feature that enables changing the granularity of the instrumentation on the fly without recompiling and redeploying the application.</p><p>T-Trace is also able to mix the instrumentation language with the main language. For example, the agent script can be written in JavaScript to trace Ruby or C++ application.</p><h3>References</h3><ul><li>Demo code: <a href="https://github.com/pavolloffay/graalvm-t-trace">https://github.com/pavolloffay/graalvm-t-trace</a></li><li>T-Trace: <a href="https://github.com/oracle/graal/blob/master/tools/docs/T-Trace.md">https://github.com/oracle/graal/blob/master/tools/docs/T-Trace.md</a></li><li>T-Trace, polyglot tracing: <a href="https://github.com/oracle/graal/blob/master/tools/docs/T-Trace-Manual.md#trully-polyglot---t-trace-any-language">https://github.com/oracle/graal/blob/master/tools/docs/T-Trace-Manual.md#trully-polyglot---t-trace-any-language</a></li><li>T-Trace embedded tracing: <a href="https://github.com/oracle/graal/blob/master/tools/docs/T-Trace-Embedding.md">https://github.com/oracle/graal/blob/master/tools/docs/T-Trace-Embedding.md</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d8b79b755e71" width="1" height="1" alt=""><hr><p><a href="https://medium.com/jaegertracing/t-trace-agent-like-instrumentation-for-graalvm-d8b79b755e71">T-Trace: agent-like instrumentation for GraalVM</a> was originally published in <a href="https://medium.com/jaegertracing">JaegerTracing</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Data analytics with Jaeger aka traces tell us more!]]></title>
            <link>https://medium.com/jaegertracing/data-analytics-with-jaeger-aka-traces-tell-us-more-973669e6f848?source=rss-a20a4172f9c------2</link>
            <guid isPermaLink="false">https://medium.com/p/973669e6f848</guid>
            <category><![CDATA[distributed-tracing]]></category>
            <category><![CDATA[opentracing]]></category>
            <category><![CDATA[jaegertracing]]></category>
            <dc:creator><![CDATA[Pavol Loffay]]></dc:creator>
            <pubDate>Sun, 02 Feb 2020 23:01:01 GMT</pubDate>
            <atom:updated>2020-02-04T15:15:20.739Z</atom:updated>
            <content:encoded><![CDATA[<p>I will get straight to the point, Jaeger at the moment only visualizes collected data from instrumented applications. It does not perform any post-processing (except service dependency diagram) or any calculations to derive other interesting metrics or features from traces it collects. This is a pity because traces contain the richest information from all telemetry signals combined!</p><p>In Jaeger we want to address this problem and provide a platform for data scientists and operators to verify a hypothesis and ultimately answer the question of what caused an outage or why the system behaves in a certain way. Apart from on-demand incident investigation, the goal is to also derive insights from all traces Jaeger collects as part of the standard Jaeger deployment. So let’s have a look at some use cases and then at the technical details.</p><h3>Metrics</h3><p>Before we deep dive into the platform overview I would like to talk about what metrics can be derived from traces. Certainly different post-processing jobs will produce results of different structures, but for now let’s only have a look at metrics as these can be directly consumed by metric systems so the solution does not require any custom storage and presentation layer.</p><h4>Network latency</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/415/1*InOjejt4yk80TAkpDL0ruw.jpeg" /><figcaption>Network latency between Service A and Service B.</figcaption></figure><p>A trace contains end-to-end information about the request/transaction. By doing some minimal calculations we are able to derive network latency between client and server calls. The results can be exported as a histogram and partitioned by client and server service labels. Example results in Prometheus metrics:</p><pre>network_latency_seconds_bucket{client=&quot;frontend&quot;,server=&quot;driver&quot;,le=&quot;0.005&quot;,} 8.0<br>network_latency_seconds_bucket{client=&quot;frontend&quot;,server=&quot;driver&quot;,le=&quot;0.01&quot;,} 9.0 network_latency_seconds_bucket{client=&quot;frontend&quot;,server=&quot;driver&quot;,le=&quot;0.025&quot;,} 12.0 network_latency_seconds_bucket{client=&quot;frontend&quot;,server=&quot;driver&quot;,le=&quot;0.05&quot;,} 15.0 network_latency_seconds_bucket{client=&quot;frontend&quot;,server=&quot;driver&quot;,le=&quot;0.075&quot;,} 20.0</pre><p>A further improvement would be to use host as a label, as a service may be load balanced across multiple hosts, with different network characteristic.</p><p>Another variation of this metric might be a duration between a consumer and a producer in messaging systems.</p><h4>Trace and service depth</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/416/1*Hco4wiVFf7QnFoXQt_GL2g.jpeg" /><figcaption>Call graph with service depth of 3 — the maximum number of hops between root service and leaf services.</figcaption></figure><p>Sometimes it is important to validate the structure of the call graph in our microservice architecture. For instance, we might be interested to know what is the maximal depth of our call graph which can be used to find outlier traces with unusual deep structures.</p><p>Service depth is the maximum network hops between root span and leaf spans.</p><h4>Service dependencies</h4><p>Another metric that falls into a trace structure category are:</p><ul><li>a number of dependencies.</li><li>a number of dependent services of a service.</li></ul><h4>Trace quality</h4><p>There is no doubt that proper tracing instrumentation is the most difficult part of rolling out tracing infrastructure in an organization. Therefore it is fundamental to measure how well applications are instrumented to evaluate tracing adoption. These metrics can be used:</p><ul><li>jaeger_client_version an appropriate Jaeger client version is used in an application.</li><li>server_span and client_span — a trace contains right combination of server and client spans. For instance, if there is a client span there should be an appropriate server span. The same metric could be used for messaging spans — producer and consumer, to find out whether both ends of the channel are instrumented.</li><li>unique_span_id — spans within a trace contain unique span id.</li></ul><pre>trace_quality_minimum_client_version_total{pass=&quot;false&quot;,service=&quot;route&quot;,version=&quot;Go-2.21.1&quot;,} 100.0 trace_quality_minimum_client_version_total{pass=&quot;false&quot;,service=&quot;redis&quot;,version=&quot;Go-2.21.1&quot;,} 119.0 trace_quality_minimum_client_version_total{pass=&quot;false&quot;,service=&quot;customer&quot;,version=&quot;Go-2.21.1&quot;,} 10.0 trace_quality_minimum_client_version_total{pass=&quot;false&quot;,service=&quot;driver&quot;,version=&quot;Go-2.21.1&quot;,} 10.0 trace_quality_server_tag_total{pass=&quot;false&quot;,service=&quot;mysql&quot;,} 10.0 trace_quality_server_tag_total{pass=&quot;true&quot;,service=&quot;customer&quot;,} 8.0 trace_quality_client_tag_total{pass=&quot;true&quot;,service=&quot;frontend&quot;,} 67.0 trace_quality_client_tag_total{pass=&quot;false&quot;,service=&quot;driver-client&quot;,} 2.0</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ovFEzngg4GF3Cy1-B6MPXA.png" /><figcaption>Trace quality dashboard in Grafana. Currently showing quality results for frontend service.</figcaption></figure><p>These quality metrics were initially open-sourced in Jaeger’s <a href="https://github.com/jaegertracing/jaeger-analytics-flink">Flink repository</a> by Uber. The solution calculates metrics and stores the results in a Cassandra table. The results are ultimately just counters, therefore we can export them to any metric system. However, the former solution also provides links to traces that fail a certain quality indicator. Which was proven to be very useful. To fully support this feature we have to wait until OSS <a href="https://www.youtube.com/watch?v=TzNZIEvhAdA">metric systems support trace exemplars</a>. However, even trace exemplars might not be sufficient if hundreds of traces will have to be linked to a single metric data point.</p><h3>Trace DSL</h3><p>Okay, we have talked about some use cases and we have defined our goal to provide a platform where these use cases could be easily implemented and executed as part of the standard Jaeger deployment. To make it dead easy to write aggregation jobs, filtering, navigation within a trace and feature extraction we should also provide an API and a library to process a trace or set of traces.</p><p>A trace is a directed acyclic graph, therefore it makes a lot of sense to represent it as a graph. We have decided to reuse an existing graph API and query/traversal language <a href="https://tinkerpop.apache.org/gremlin.html">Gremlin</a> from the Apache TinkerPop project. The project also provides an in-memory database <a href="http://tinkerpop.apache.org/docs/current/reference/#tinkergraph-gremlin">TinkerGraph</a> which we use once we load traces from the storage (Kafka, Jaeger-query).</p><p>Let’s have a look at some examples of our Trace DSL. The first example answers the question “Is there a client span with duration 120 microseconds?”</p><pre>TraceTraversal&lt;Vertex, Vertex&gt; traversal =      <br>  graph.traversal(TraceTraversalSource.class)<br>    .hasTag(Tags.<em>SPAN_KIND</em>.getKey(), Tags.<em>SPAN_KIND_CLIENT</em>)<br>    .duration(P.<em>gt</em>(120));</pre><p>As you might have noticed the query uses two methods from Trace DSL: hasTag and duration . These methods were added to Gremlin core API via TraceTraversalSource.class. The result is a list of vertices/spans that satisfy this query. From the vertices/spans we can navigate to other parts of the trace.</p><p>The second example answers the question: “Is span with a name child2 a direct descendant of root span?”.</p><pre>TraceTraversal&lt;Vertex, Vertex&gt; traversal = <br>  graph.traversal(TraceTraversalSource.class)<br>    .hasName(&quot;root&quot;)<br>    .repeat(__.<em>out</em>())<br>    .until(__.<em>hasName</em>(&quot;child2&quot;));</pre><p>This query is more complicated and it uses core Gremlin API calls like repeat(__.out()) to traverse to outgoing edges. It also makes sense to provide this query as an extension to Gremlin API if it becomes commonly used. I acknowledge that writing Gremlin queries is not trivial, hence feature full Trace DSL should simplify the work.</p><h3>Architecture</h3><p>The following diagram depicts Jaeger streaming architecture with data analytics integration.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*G_o6qmBMEHfaDm8QEJ6EVQ.jpeg" /><figcaption>Jaeger streaming architecture diagram with data analytics integration.</figcaption></figure><p>There are two parts of the analytics platform: Spark streaming for all incoming data and on-demand Jupyter notebook.</p><p>The Spark streaming connects to the same Kafka topic used by the Jaeger collection pipeline. It consumes and analyzes the data, exposing the results as Prometheus metrics or writing them to the storage.</p><p>The second integration path is done via Jupyter notebooks. The notebook can connect to Kafka to get the stream of data or get the historical data from Jaeger query. Then do the analysis and show results in the notebook or publish to Prometheus or the storage.</p><h3>Conclusion</h3><p>We have talked about the reasons and use cases for data analytics platform for Jaeger. Our goal is to build a community for this effort to help develop the models but also validate them on real deployments. Ultimately, analytics features should provide us more insights into our application behavior and a power-user interface for incident analysis. The project is still in early development and we would like to hear your feedback! Do not hesitate to reach out to us directly or create a feature request in the <a href="https://github.com/jaegertracing/jaeger-analytics-java">repository</a>.</p><h3>References</h3><ul><li>Jaeger Java analytics: <a href="https://github.com/jaegertracing/jaeger-analytics-java">https://github.com/jaegertracing/jaeger-analytics-java</a></li><li>Jaeger analytics flink: <a href="https://github.com/jaegertracing/jaeger-analytics-flink">https://github.com/jaegertracing/jaeger-analytics-flink</a></li><li>Apache TinkerPop Gremlin: <a href="https://tinkerpop.apache.org/gremlin.html">https://tinkerpop.apache.org/gremlin.html</a></li><li>Apache TinkerGraph docs: <a href="http://tinkerpop.apache.org/docs/current/reference/#tinkergraph-gremlin">http://tinkerpop.apache.org/docs/current/reference/#tinkergraph-gremlin</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=973669e6f848" width="1" height="1" alt=""><hr><p><a href="https://medium.com/jaegertracing/data-analytics-with-jaeger-aka-traces-tell-us-more-973669e6f848">Data analytics with Jaeger aka traces tell us more!</a> was originally published in <a href="https://medium.com/jaegertracing">JaegerTracing</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Jaeger with Elasticsearch on Kubernetes using Operators]]></title>
            <link>https://medium.com/jaegertracing/jaeger-with-elasticsearch-on-kubernetes-using-operators-268863fd4346?source=rss-a20a4172f9c------2</link>
            <guid isPermaLink="false">https://medium.com/p/268863fd4346</guid>
            <category><![CDATA[tracing]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[elasticsearch]]></category>
            <category><![CDATA[jaegertracing]]></category>
            <category><![CDATA[jaeger]]></category>
            <dc:creator><![CDATA[Pavol Loffay]]></dc:creator>
            <pubDate>Mon, 16 Dec 2019 13:01:02 GMT</pubDate>
            <atom:updated>2020-01-27T12:18:24.164Z</atom:updated>
            <content:encoded><![CDATA[<p>In this article, we are going to look at deploying Jaeger on Kubernetes and OpenShift with Elasticsearch storage using operators. We will go through the configuration (custom resources) and also see what benefits OpenShift offers over vanilla Kubernetes.</p><p>To get started with Jaeger operator refer to our operator <a href="https://medium.com/jaegertracing/jaeger-operator-for-kubernetes-b3128f3c4943">blog post</a> or <a href="https://www.jaegertracing.io/docs/latest/operator/">documentation</a>.</p><h3>What is the operator?</h3><p>Kubernetes operator is a software component that simplifies deployment and operations of an application running in a Kubernetes cluster — in our case Jaeger and Elasticsearch. Think of it as software that knows how to deploy, monitor, upgrade and scale an application.</p><p>Behind the scenes, the operator uses Kubernetes <a href="https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/">custom resource</a> (CR) to expose application configuration. The operator watches the resource and accordingly it makes changes to the application configuration or deployment based on the options provided in the CR.</p><p>To make life even easier with operators there is one additional component — <a href="https://docs.openshift.com/container-platform/4.1/applications/operators/olm-understanding-olm.html">Operator Lifecycle Manager </a>(OLM). This component helps users install, update, and manage the lifecycle of all operators and their associated services running across their clusters.</p><p>To learn more about operators refer to the <a href="https://kubernetes.io/docs/concepts/extend-kubernetes/operator/">Kubernetes documentation</a>, <a href="https://docs.openshift.com/container-platform/4.1/applications/operators/olm-understanding-olm.html">OpenShift</a> or <a href="https://coreos.com/operators/">coreos documentation</a>.</p><h3>Elasticsearch operator on Kubernetes</h3><p>In this section, we are going to use <a href="https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-quickstart.html">elastic.co Elasticsearch operator</a> as backend storage for Jaeger. Refer to the Elasticsearch documentation on how to deploy the Elasticsearch operator. Jaeger configuration in this section can also be used for any external Elasticsearch cluster.</p><p>Create an Elasticsearch CR that will be used by the operator to deploy a single node Elasticsearch instance:</p><pre>cat &lt;&lt;EOF | kubectl apply -f -<br>apiVersion: elasticsearch.k8s.elastic.co/v1<br>kind: Elasticsearch<br>metadata:<br>  name: quickstart<br>spec:<br>  version: 7.5.0<br>  nodeSets:<br>  - name: default<br>    count: 1<br>    config:<br>      node.master: true<br>      node.data: true<br>      node.ingest: true<br>      node.store.allow_mmap: false<br>EOF</pre><p>Before deploying Jaeger we have to get the password from Elasticsearch secret and create a new secret for Jaeger with username and password. Alternatively, we could specify these two options directly in storage options in Jaeger CR.</p><pre>PASSWORD=$(kubectl get secret quickstart-es-elastic-user -o=jsonpath=&#39;{.data.elastic}&#39; | base64 --decode)</pre><pre>kubectl create secret generic jaeger-secret --from-literal=ES_PASSWORD=${PASSWORD} --from-literal=ES_USERNAME=elastic</pre><p>Now the secret jaeger-secretwith username and password for Elasticsearch has been created and we can specify it in spec.storage.secretName within the Jaeger CR. We have to also specify Elasticsearch URL and injected CA certificate as a volume from quickstart-es-http-certs-public secret.</p><pre>cat &lt;&lt;EOF | kubectl apply -f -<br>apiVersion: jaegertracing.io/v1<br>kind: Jaeger<br>metadata:<br>  name: simple-prod<br>spec:<br>  strategy: production<br>  storage:<br>    type: elasticsearch<br>    options:<br>      es:<br>        server-urls: <a href="https://quickstart-es-http:9200">https://quickstart-es-http:9200</a><br>        tls:<br>          ca: /es/certificates/ca.crt<br>        num-shards: 1<br>        num-replicas: 0<br>    secretName: jaeger-secret<br>  volumeMounts:<br>    - name: certificates<br>      mountPath: /es/certificates/<br>      readOnly: true<br>  volumes:<br>    - name: certificates<br>      secret:<br>        secretName: quickstart-es-http-certs-public<br>EOF</pre><p>The above configuration works since Jaeger 1.16.0 (<a href="https://github.com/jaegertracing/jaeger/pull/1918">#PR1918</a>). Older Jaeger versions should deploy, however the cron job to clean old data will fail on TLS related errors. Note that spark-dependencies also does not support TLS (<a href="https://github.com/jaegertracing/spark-dependencies/issues/83">#ISSUE83</a>).</p><p>The successful deployment can be verified by:</p><pre><br>kubectl get deployment                                                                                                                                                                                                                                                                               <br>NAME                    READY     UP-TO-DATE   AVAILABLE   AGE<br>simple-prod-collector   1/1       1            1           4m12s<br>simple-prod-query       1/1       1            1           4m12s</pre><p>Now let’s have a look at deploying Jaeger on OpenShift using self-provisioning of Elasticsearch cluster.</p><h3>Openshift 4</h3><p>Within an OpenShift environment Jaeger can make use of <a href="https://docs.openshift.com/container-platform/4.1/logging/config/efk-logging-elasticsearch.html">cluster logging</a> and automatically provision an Elasticsearch cluster based on the configuration in Jaeger CR. OpenShift 4 also comes by default with OLM and user interface, therefore Jaeger provisioning can be done directly in the user interface. We will show how to deploy via OLM’s user interface. The same could be accomplished by directly deploying operators without OLM, however we will use benefits of “click on a button” updates and other management features OLM provides.</p><p>First we have to deploy Jaeger and Elasticsearch operator via OLM. Open OpenShift UI and navigate to Operators and OperatorHub menu and search for Elasticsearch and Jaeger.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VJUu3qdOEDAnOSDYl_g1Ag.png" /><figcaption>Deploy Elasticsaerch cluster logging operator.</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fqRR5JLiK978_m1MBNdLqA.png" /><figcaption>Deploy Jaeger operator.</figcaption></figure><p>Then we can go to Installed Operators menu to verify operators were deployed. There we click on Jaeger and create an instance.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_nTI5FgY94dxr6uRDXpyQQ.png" /><figcaption>Installed Operators menu.</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*E9h45HpVlkEPsMgKTqFKXA.png" /><figcaption>Jaeger Operator details.</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bkg-kb5ntgVklh3-hkg62A.png" /><figcaption>Create Jaeger instance with self-provisioned Elasticsearch.</figcaption></figure><p>We can also create an instance in the command line:</p><pre>cat &lt;&lt;EOF | kubectl apply -f -<br>apiVersion: jaegertracing.io/v1<br>kind: Jaeger<br>metadata:<br>  name: simple-prod<br>spec:<br>  strategy: production<br>  storage:<br>    type: elasticsearch<br>    elasticsearch:<br>      nodeCount: 1<br>      resources:<br>        requests:<br>          cpu: 200m<br>          memory: 1Gi<br>        limits:<br>          memory: 1Gi<br>EOF</pre><p>This CR is significantly simpler than the one for external Elasticsearch. First we only have to create a single CR for Jaeger and storage, additionally, we no longer have to inject certificates, user credentials or the number of primary and replica shards. All the configuration is done automatically by the operator. For instance, the operator autonomously chooses data replication factor based on the nodes in the cluster. Behind the scenes, Jaeger operator creates Elasticsearch CR (for cluster logging Elasticsearch) which triggers Elasticsearch deployment for the given configuration.</p><p>Persistent storage can be specified in.elasticsearch.storage by providing storage class name and size. Based on this configuration operator provisions persistent volume claim (PVC) and persistent volume (PV). The persistent volume is bound to the Elasticsearch instance by its name and it should be reused if the instance with the same name is re-created.</p><p>Created Jaeger instance will show up in Jaeger tab. Once we click on the instance we will see what Kubernetes objects belong to it, and we can also modify the configuration if needed.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KKN8Hia4iWqjuoNH2sMVpw.png" /><figcaption>List of Jaeger instances.</figcaption></figure><p>Let’s also have a look at created deployments by operators:</p><pre>kubectl get deployment                                                                   <br>NAME                                                    READY deployment.apps/elasticsearch-cdm-defaultsimpleprod-1   1/1       <br>deployment.apps/simple-prod-collector                   1/1     <br>deployment.apps/simple-prod-query                       1/1    </pre><p>Deployment on OpenShift also provides simplified and secure access to Jaeger console. Console URL can be obtained from the networking menu in UI or via oc get routes. To login to Jaeger we use our OpenShift credentials.</p><h3>Conclusion</h3><p>We have deployed Jaeger with Elasticsearch in two different ways. First we have used vanilla Kubernetes and upstream Elasticsearch operator. This required more configuration work and the creation of multiple Kubernetes objects. In the second scenario we have used OpenShift 4 with Operator Lifecycle Manager (OLM) user interface to provision Jaeger instance with Elasticsearch storage. This didn’t require any storage and security configuration. Last but not least OLM provides automatic updates for operators and their instances.</p><h3>References</h3><ul><li>Operator Hub <a href="https://operatorhub.io/">https://operatorhub.io/</a></li><li>Jaeger operator documentation <a href="https://www.jaegertracing.io/docs/1.15/operator/">https://www.jaegertracing.io/docs/1.15/operator/</a></li><li>Elasticsearch operator <a href="https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-quickstart.html">https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-quickstart.html</a></li><li>Self-provisioned Elasticsearch <a href="https://www.jaegertracing.io/docs/1.15/operator/#self-provisioned">https://www.jaegertracing.io/docs/1.15/operator/#self-provisioned</a></li><li>Elasticsearch cluster logging operator <a href="https://docs.openshift.com/container-platform/4.1/logging/config/efk-logging-elasticsearch.html">https://docs.openshift.com/container-platform/4.1/logging/config/efk-logging-elasticsearch.html</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=268863fd4346" width="1" height="1" alt=""><hr><p><a href="https://medium.com/jaegertracing/jaeger-with-elasticsearch-on-kubernetes-using-operators-268863fd4346">Jaeger with Elasticsearch on Kubernetes using Operators</a> was originally published in <a href="https://medium.com/jaegertracing">JaegerTracing</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Using Elasticsearch Rollover to manage indices]]></title>
            <link>https://medium.com/jaegertracing/using-elasticsearch-rollover-to-manage-indices-8b3d0c77915d?source=rss-a20a4172f9c------2</link>
            <guid isPermaLink="false">https://medium.com/p/8b3d0c77915d</guid>
            <category><![CDATA[jaegertracing]]></category>
            <category><![CDATA[elasticsearch]]></category>
            <dc:creator><![CDATA[Pavol Loffay]]></dc:creator>
            <pubDate>Wed, 24 Jul 2019 11:52:28 GMT</pubDate>
            <atom:updated>2019-07-25T11:10:33.282Z</atom:updated>
            <content:encoded><![CDATA[<p>In this article you will learn how to configure and use the <a href="https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-rollover-index.html">Elasticsearch rollover</a> feature in Jaeger. Note that this feature has been introduced in Jaeger 1.10.0.</p><p>Jaeger uses index-per-day pattern to store its data to Elasticsearch. It creates a new index for each day based on span’s timestamp. These indices have to be periodically removed by jaeger-es-index-cleaner cron job. Typically users keep data from one week up to one month which results in 7 or 30 indices only for spans.</p><p>Index-per-day pattern might not be effective if data or resources are not evenly distributed. For example indices which do not contain any data still allocate shards or an index might contain significantly more data than the others. These use-cases result in sub-optimal use of resources.</p><h3>How rollover works</h3><p>In contrast to index-per-day management rollover uses an index alias which can rollover to a new index based on configured conditions. In practice, this means there are two aliases one for reading and another for writing. The read alias points to a set of read-only indices and write alias to one write index.</p><p>These are the conditions used for rolling over to a new index:</p><ul><li>max_age — the maximum age of the index</li><li>max_docs — the maximum number of documents the index should contain</li><li>max_size — the maximum estimated size of primary shards (since Elasticsearch 6.x)</li></ul><p>The rollover REST API has to be called periodically, the conditions are evaluated only during the REST call and they are not stored in Elasticsearch. Therefore a cron job has to be configured to make use of this feature.</p><h3>Configuration</h3><p>The Jaeger configuration of rollover consists of several steps.</p><h4>1. Initial configuration</h4><p>Before deploying Jaeger it is mandatory to initialize the feature by running jaeger-es-rollover docker image (assuming Elasticsearch runs on localhost). This command creates read/write aliases and write indices.</p><pre>docker run -it --rm --net=host jaegertracing/jaeger-es-rollover:latest init <a href="http://localhost:9200">http://localhost:9200</a></pre><p>Run the command again with-e ARCHIVE=true if you want to use this feature also with the archive index.</p><p>Once this is done Jaeger can be deployed with rollover enabled --es.use-aliases=true and --es.archive.use-aliases=true (for archive storage):</p><pre>docker run -it --rm --net=host -e SPAN_STORAGE_TYPE=elasticsearch jaegertracing/all-in-one:latest --es.use-aliases=true --es-archive.enabled=true --es-archive.use-aliases=true</pre><h4>2. Rolling over to a new index</h4><p>Jaeger has been deployed and now it writes data to a write alias. The next step is to periodically execute rollover API which rolls the write alias to a new index based on supplied conditions. The command also adds a new index to read alias to make new data available for search. The following command rolls the alias over to a new index if the age of the current write index is older than 1 second. We use this short time interval <em>just for demo purposes</em> to make sure rollover takes place.</p><pre>docker run -it --rm --net=host -e CONDITIONS=&#39;{&quot;max_age&quot;: &quot;1s&quot;}&#39; jaegertracing/jaeger-es-rollover:latest rollover  <a href="http://localhost:9200">http://localhost:920</a>0</pre><p>Run the same command with -e ARCHIVE=true for the archive storage.</p><p>The next step is to remove old indices from read aliases. It means that old data will not be available for search. This imitates the behavior of --es.max-span-age flag used in default index-per-day deployment. This step could be optional and old indices could be simply removed by index cleaner in the next step.</p><pre>docker run -it --rm --net=host -e UNIT=seconds -e UNIT_COUNT=1 jaegertracing/jaeger-es-rollover:latest lookback  <a href="http://localhost:9200">http://localhost:920</a>0</pre><p>Run the same command with -e ARCHIVE=true for the archive storage.</p><h4>3. Removing old data</h4><p>The old data from Elasticsearch has to be periodically removed by deleting old indices. In this case we are running index cleaner with the parameter0 which removes all indices including the ones created today.</p><pre>docker run -it --rm --net=host -e ROLLOVER=true jaegertracing/jaeger-es-index-cleaner:latest 0 <a href="http://localhost:9200">http://localhost:9200</a></pre><p>Run the same command with -e ARCHIVE=true for the archive storage.</p><p>Note that this functionality is broken at the moment and it will be available in the next release (1.13.1/1.14.0). The issue is tracked in <a href="https://github.com/jaegertracing/jaeger/issues/1681">#1681</a>.</p><h3>Rollover in Jaeger Operator</h3><p>Rollover feature can be enabled in jaeger-operator be specifying --es.use-aliases=true in storage flags. The operator will automatically initialize the aliases and configure rollover cron jobs. The rollover conditions can be specified in theesRollover section under storage. Please refer to the <a href="https://github.com/jaegertracing/jaeger-operator">operator documentation</a> for more details.</p><h3>Migration from daily indices to rollover</h3><p>Migration from default index-per-day deployment is possible by manually adding old indices to read alias.</p><pre>curl -ivX POST -H &quot;Content-Type: application/json&quot; localhost:9200/_aliases -d &#39;{<br>    &quot;actions&quot; : [<br>        { &quot;add&quot; : { &quot;index&quot; : &quot;jaeger-span-*-*-*&quot;, &quot;alias&quot; : &quot;jaeger-span-read&quot; } },<br> { &quot;add&quot; : { &quot;index&quot; : &quot;jaeger-service-*-*-*&quot;, &quot;alias&quot; : &quot;jaeger-service-read&quot; } }<br>    ]<br>}&#39;</pre><p>And similarly for archive index:</p><pre>curl -ivX POST -H &quot;Content-Type: application/json&quot; localhost:9200/_aliases -d &#39;{<br>    &quot;actions&quot; : [<br>        { &quot;add&quot; : { &quot;index&quot; : &quot;jaeger-span-archive&quot;, &quot;alias&quot; : &quot;jaeger-span-archive-read&quot; } }<br>    ]<br>}&#39;</pre><h3>Useful links</h3><ul><li>Elasticsearch rollover: <a href="https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-rollover-index.html">https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-rollover-index.html</a></li><li>Jaeger operator for Kubernetes <a href="https://github.com/jaegertracing/jaeger-operator/">https://github.com/jaegertracing/jaeger-operator</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8b3d0c77915d" width="1" height="1" alt=""><hr><p><a href="https://medium.com/jaegertracing/using-elasticsearch-rollover-to-manage-indices-8b3d0c77915d">Using Elasticsearch Rollover to manage indices</a> was originally published in <a href="https://medium.com/jaegertracing">JaegerTracing</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>