Coder Social home page Coder Social logo

chrisguidry / pytest-opentelemetry Goto Github PK

View Code? Open in Web Editor NEW
17.0 6.0 5.0 53 KB

A pytest plugin for instrumenting test runs via OpenTelemetry

License: MIT License

Python 97.91% Makefile 2.09%
jaeger jaegertracing observability opentelemetry opentelemetry-collector pytest pytest-plugin telemetry unit-testing

pytest-opentelemetry's Introduction

pytest-opentelemetry

Instruments your pytest runs, exporting the spans and timing via OpenTelemetry.

Why instrument my test suite?

As projects grow larger, perhaps with many contributors, test suite runtime can be a significant limiting factor to how fast you and your team can deliver changes. By measuring your test suite's runtime in detail, and keeping a history of this runtime in a visualization tool like Jaeger, you can spot test bottlenecks that might be slowing your entire suite down.

Additionally, pytest makes an excellent driver for integration tests that operate on fully deployed systems, like your testing/staging environment. By using pytest-opentelemetry and configuring the appropriate propagators, you can connect traces from your integration test suite to your running system to analyze failures more quickly.

Even if you only enable pytest-opentelemetry locally for occasional debugging, it can help you understand exactly what is slowing your test suite down. Did you forget to mock that requests call? Didn't realize the test suite was creating 10,000 example accounts? Should that database setup fixture be marked scope=module? These are the kinds of questions pytest-opentelemetry can help you answer.

pytest-opentelemetry works even better when testing applications and libraries that are themselves instrumented with OpenTelemetry. This will give you deeper visibility into the layers of your stack, like database queries and network requests.

Installation and usage

pip install pytest-opentelemetry

Installing a library that exposes a specific pytest-related entry point is automatically loaded as a pytest plugin. Simply installing the plugin should be enough to register it for pytest.

Using the --export-traces flag enables trace exporting (otherwise, the created spans will only be tracked in memory):

pytest --export-traces

By default, this exports traces to http://localhost:4317, which will work well if you're running a local OpenTelemetry Collector exposing the OTLP gRPC interface. You can use any of the OpenTelemetry environment variables to adjust the tracing export or behavior:

export OTEL_EXPORTER_OTLP_ENDPOINT=http://another.collector:4317
pytest --export-traces

Only the OTLP over gRPC exporter is currently supported.

pytest-opentelemetry will use the name of the project's directory as the OpenTelemetry service.name, but it will also respect the standard OTEL_SERVICE_NAME and OTEL_RESOURCE_ATTRIBUTES environment variables. If you would like to permanently specify those for your project, consider using the very helpful pytest-env package to set these for all test runs, for example, in your pyproject.toml:

[tool.pytest.ini_options]
env = [
    "OTEL_RESOURCE_ATTRIBUTES=service.name=my-project",
]

If you are using the delightful pytest-xdist package to spread your tests out over multiple processes or hosts, pytest-opentelemetry will automatically unite them all under one trace. If this pytest run is part of a larger trace, you can provide a --trace-parent argument to nest this run under that parent:

pytest ... --trace-parent 00-1234567890abcdef1234567890abcdef-fedcba0987654321-01

Visualizing test traces

One quick way to visualize test traces would be to use an OpenTelemetry Collector feeding traces to Jaeger. This can be configured with a minimal Docker Compose file like:

version: "3.8"
services:
  jaeger:
    image: jaegertracing/all-in-one:1.33
    ports:
    - 16686:16686    # frontend
    - 14250:14250    # model.proto
  collector:
    image: otel/opentelemetry-collector-contrib:0.49.0
    depends_on:
    - jaeger
    ports:
    - 4317:4317      # OTLP (gRPC)
    volumes:
    - ./otelcol-config.yaml:/etc/otelcol-contrib/config.yaml:ro

With this otelcol-config.yaml:

receivers:
  otlp:
    protocols:
      grpc:

processors:
  batch:

exporters:
  jaeger:
    endpoint: jaeger:14250
    tls:
      insecure: true

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [jaeger]

Developing

Two references I keep returning to is the pytest guide on writing plugins, and the pytest API reference:

These are extremely helpful in understanding the lifecycle of a pytest run.

To get setup for development, you will likely want to use a "virtual environment", using great tools like virtualenv or pyenv.

Once you have a virtual environment, install this package for editing, along with its development dependencies, with this command:

pip install -e '.[dev]'

When sending pull requests, don't forget to bump the version in setup.cfg.

pytest-opentelemetry's People

Contributors

chrisguidry avatar cpnat avatar drcraig avatar ljodal avatar yamakaky avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

pytest-opentelemetry's Issues

Better service name detection

Currently the service name is taken from the directory name. The standard way is to use the env variable OTEL_SERVICE_NAME. That's what Resource.create() does by default. Maybe have a priority list like env variable > entry in pyproject.toml > directory name.

https://github.com/chrisguidry/pytest-opentelemetry/blob/main/src/pytest_opentelemetry/resource.py

https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md#general-sdk-configuration

Root span should be marked a failure if any test fails

Reported by @Yamakaky in #11, splitting out here to work on independently

Currently when a single test fails, it's span is correctly considered a failure, but the overall test's span is still considered a success. This doesn't match expectations, since when you have a single failed pytest test, your entire test suite is considered failed (pytest will return an exit code after running, etc.)

Set root span status

The root span stays is unset even if a test fails.

(putting it here since I don't know how to fix it)

Collect spans for fixture setup and teardown

Hey @chrisguidry! Long time, no see, hope you're doing well. This is a beautiful little tool :)

I had an idea that I'd be happy to help implement if you think you'd want to include it: collecting spans for fixture setup and teardown, in addition to the tests themselves. In my usecase, we are testing hardware, and the setup and teardown can be quite time consuming, so we're eager to find which fixtures are taking the most time. Maybe these long running fixtures can be refactored to be faster, or maybe that can be bumped up to be module or session scoped.

To implement, I think I'd envision there being a span for the entire test case covering setup, test, and teardown, and then underneath that would be spans for for the fixture setups, the actual test case, and then the fixture teardowns. Maybe that could be as simple as doing the same thing you have in pytest_runtest_protocol but for pytest_runtest_setup, pytest_runtest_call, and pytest_runtest_teardown? I'll see if I can experiment on that some time soon.

Cleaner spans

This is a meta issue about a few things that could be improved about metadata of the spans. I'm doing some tests about what would be best, and should push a PR at some point.

  • The root span is named "test run" with no meaningful attributes. It would be nice to be able to customize it. Not sure how though, maybe an env variable for start?
  • The span name uses node.nodeid for its name instead of node.name which doesn't include the filename. This info can be found in code.filepath anyway.
  • code.function should use item.name.
  • code.lineno can be 0 when running from a Tavern yaml file, maybe only include it when > 0. Otel supports ints so no need to str(line_number).
  • (wont do) start_as_current_span has optional attributes that can auto record exceptions https://opentelemetry-python.readthedocs.io/en/latest/api/trace.html#opentelemetry.trace.Tracer.start_as_current_span

Ability to control traces such that push trace data only for pytest functions that exceed a particular run time limit

This is a suggestion to have a threshold so that trace data is pushed only for pytest functions that exceed a particular run time limit.

Background : I have a test suite that has 6000 odd pytest functions that are run by a single pytest process.

90% of these are quite fast. However the rest take longer.

Using pytest-opentelemetry we were able push the traces to Jaegar UI, but analysing the statistics to identify some of the most used test helper operations was difficult due to the volume of spans under a single trace.

I understand this is a drawback on Jaeger UI, but if we have a control to push only the traces for long running test functions it would be helpful.

A sample snapshot of Jaeger UI statistics that I am trying to view.
image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.