Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow user-defined tags based on response data for built-in metrics (e.g. value of a certain HTTP header) #3940

Open
frittentheke opened this issue Sep 9, 2024 · 4 comments
Assignees

Comments

@frittentheke
Copy link

Feature Description

I know it's possible to add tags at various stages and abstraction levels: https://grafana.com/docs/k6/latest/using-k6/tags-and-groups/#user-defined-tags. But this has to happen before the operation and apparently it's not possible to dynamically extract a tag based on the response data, e.g. when calling fail.

In my case I'd like to extract more details from 4xx or 5xx errors (certain HTTP headers indicating a more detailed reason for the failure). I know I can create a custom metric and apply a tag based on the response. But I'd much rather add another tag or two to the built-in metrics.

A somewhat related question might be this one here: #680

Suggested Solution (optional)

No response

Already existing or connected issues / PRs (optional)

No response

@joanlopez
Copy link
Contributor

Hey @frittentheke,

Thanks for your request. Could you please extend your proposal?
Like, how these tags would be defined, and accounted, with a example script.

We would appreciate a detailed feature request, so we can dig straight into possible implementations, their the pros & cons, feasibility, etc - rather than designing a new feature from scratch.

🙇🏻

@frittentheke
Copy link
Author

Thanks @joanlopez for getting back to me this quickly!

Thanks for your request. Could you please extend your proposal? Like, how these tags would be defined, and accounted, with a example script.

Certainly! The sole difference to the existing capabilities of the check function is the time additional (user-defined) tags are defined.

Currently one has to set the tags and their value statically prior to calling check():

import http from 'k6/http';
import { check, fail } from 'k6';

export default function () {
  const res = http.get('https://httpbin.test.k6.io');
  const checkOutput = check(
    res,
    {
      'response code was 200': (res) => res.status == 200,
      'body size was 1234 bytes': (res) => res.body.length == 1234,
    },
    { myTag: "I'm a tag" }
  );

  if (!checkOutput) {
    fail('unexpected response');
  }
}

I like to use data from the (HTTP) response though to enrich e.g. the k6_http_req_failed_rate(HTTPReqFailed) with tag apart from error, error_code, status, ...

In my particular case the intended tag values come from certain headers in the 5xx and 4xx responses to which currently (

func (t *transport) measureAndEmitMetrics(unfReq *unfinishedRequest) *finishedRequest {
?) only static tags can be applied. To further point to my particular use-case, take the x-envoy-overloaded header (envoyproxy/envoy#15106) which I'd like to distinguish in my metrics from other 503 responses - so to separate 503 from Envoy (Istio Service Mesh) and the application itself.

But my use-case is NOT limited to failed requests. Successful responses could also be distinguished by certain headers and their value in the response, for example by indicating if a request was served from a cache (e.g. AWS Cloudfront X-Cache header).

While I certainly can introduce a [custom metric ](https://grafana.com/docs/k6/latest/using-k6/metrics/create-custom-metrics) and extract the headers before e.g. calling fail(), I much rather like for all those rich built-in metrics (

func (t *transport) measureAndEmitMetrics(unfReq *unfinishedRequest) *finishedRequest {
) to be distinct for certain tags.

@joanlopez
Copy link
Contributor

Thanks for the extended answer @frittentheke! 🙇🏻

Could you provide one or two ways of how would you like to define such metrics, with an example like the one you provided to show how it could be achieved now? Like, having a setTag (or similar) function that would be called within the check callback? Or what?

Thanks!

@frittentheke
Copy link
Author

Could you provide one or two ways of how would you like to define such metrics, with an example like the one you provided to show how it could be achieved now? Like, having a setTag (or similar) function that would be called within the check callback? Or what?

Currently one has to use a custom metric to use data from the response:

export function httpErrorCheck(response: Response): void {
    if (response.status !== 200) {
        httpErrorCounter.add(1, { "x-envoy-overloaded": response.headers["x-envoy-overloaded"] } })
        fail(`Response Status Code was: ${response.status}`);
    }
}
  1. I'd either like user defined tags which are NOT static, but reference something from the response. Or the ability to add a list of response headers to the list of system-tags. Those are also referencing information from the response e.g. tls_version.

With just defining headers that should be extracted and added to the built-in metrics as tags, this solution is a little clunky.

  1. As you, @joanlopez , suggested, a configurable callback to "extract" and set tags from the response makes the most sense. There already is so much data extraction towards tags happening in
    if unfReq.err != nil {
    result.errorCode, result.errorMsg = errorCodeForError(unfReq.err)
    tagsAndMeta.SetSystemTagOrMetaIfEnabled(enabledTags, metrics.TagError, result.errorMsg)
    tagsAndMeta.SetSystemTagOrMetaIfEnabled(enabledTags, metrics.TagErrorCode, strconv.Itoa(int(result.errorCode)))
    tagsAndMeta.SetSystemTagOrMetaIfEnabled(enabledTags, metrics.TagStatus, "0")
    } else {
    tagsAndMeta.SetSystemTagOrMetaIfEnabled(enabledTags, metrics.TagStatus, strconv.Itoa(unfReq.response.StatusCode))
    if unfReq.response.StatusCode >= 400 {
    result.errorCode = errCode(1000 + unfReq.response.StatusCode)
    tagsAndMeta.SetSystemTagOrMetaIfEnabled(enabledTags, metrics.TagErrorCode, strconv.Itoa(int(result.errorCode)))
    }
    tagsAndMeta.SetSystemTagOrMetaIfEnabled(enabledTags, metrics.TagProto, unfReq.response.Proto)
    if unfReq.response.TLS != nil {
    tlsInfo, oscp := netext.ParseTLSConnState(unfReq.response.TLS)
    tagsAndMeta.SetSystemTagOrMetaIfEnabled(enabledTags, metrics.TagTLSVersion, tlsInfo.Version)
    tagsAndMeta.SetSystemTagOrMetaIfEnabled(enabledTags, metrics.TagOCSPStatus, oscp.Status)
    result.tlsInfo = tlsInfo
    }
    }
    if enabledTags.Has(metrics.TagIP) && trail.ConnRemoteAddr != nil {
    if ip, _, err := net.SplitHostPort(trail.ConnRemoteAddr.String()); err == nil {
    tagsAndMeta.SetSystemTagOrMeta(metrics.TagIP, ip)
    }
    }
    . Referencing a little functiondynamicTagsCallback to allow for more data extraction and return of an additional list of tags and their value would be awesome.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants