Carlos Aguni

Highly motivated self-taught IT analyst. Always learning and ready to explore new skills. An eternal apprentice.


K6 GRPC/HTTP Stress Test

20 Sep 2022 »

Otel collector

git clone https://github.com/open-telemetry/opentelemetry-collector-contrib.git
cd opentelemetry-collector-contrib/examples/demo

docker-compose.yml

version: "2"
services:

  # Jaeger
  jaeger-all-in-one:
    image: jaegertracing/all-in-one:latest
    ports:
      - "16686:16686"
        #- "14268:14268"
      - "14250"

  # Collector
  otel-collector:
    image: ${OTELCOL_IMG}
    command: ["--config=/etc/otel-collector-config.yaml", "${OTELCOL_ARGS}"]
    volumes:
      - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
    ports:
      - "1888:1888"   # pprof extension
      - "8888:8888"   # Prometheus metrics exposed by the collector
      - "8889:8889"   # Prometheus exporter metrics
      - "13133:13133" # health_check extension
      - "4317:4317"   # OTLP gRPC receiver
      - "4318:4318"   # OTLP HTTP receiver
      - "14268:14268"   # Jaeger HTTP receiver
      - "55679:55679" # zpages extension
    depends_on:
      - jaeger-all-in-one

otel-collector-config.yaml

receivers:
  jaeger:
    protocols:
      thrift_http: # 14268
  otlp:
    protocols:
      grpc:
      http:
        cors:
           allowed_origins:
             - '*'

exporters:
    #prometheus:
    #  endpoint: "0.0.0.0:8889"
    #  const_labels:
    #    label1: value1

  logging:
    logLevel: debug

    #zipkin:
    #  endpoint: "http://zipkin-all-in-one:9411/api/v2/spans"
    #  format: proto

  jaeger:
    endpoint: jaeger-all-in-one:14250
    tls:
      insecure: true

processors:
  batch:

extensions:
  health_check:
  pprof:
    endpoint: :1888
  zpages:
    endpoint: :55679

service:
  extensions: [pprof, zpages, health_check]
  pipelines:
    traces:
      receivers: [jaeger,otlp]
      processors: [batch]
      #exporters: [logging, zipkin, jaeger]
      exporters: [jaeger, logging]
      #exporters: [logging]
      #metrics:
      #  receivers: [otlp]
      #  processors: [batch]
      #  exporters: [logging, prometheus]

agenda.json

{
    "resourceSpans": [
        {
            "resource": {
                "attributes": [
                    {
                        "key": "service.name",
                        "value": {
                            "stringValue": "Angular Sample App"
                        }
                    },
                    {
                        "key": "telemetry.sdk.language",
                        "value": {
                            "stringValue": "webjs"
                        }
                    },
                    {
                        "key": "telemetry.sdk.name",
                        "value": {
                            "stringValue": "opentelemetry"
                        }
                    },
                    {
                        "key": "telemetry.sdk.version",
                        "value": {
                            "stringValue": "1.5.0"
                        }
                    }
                ],
                "droppedAttributesCount": 0
            },
            "scopeSpans": [
                {
                    "scope": {
                        "name": "@jufab/opentelemetry-angular-interceptor",
                        "version": "1.1.0-2"
                    },
                    "spans": [
                        {
                            "traceId": "6c697d06c5eecac7fd62fbe4d02521e2",
                            "spanId": "1c2be1cbaf16deec",
                            "name": "HTTP GET",
                            "kind": 3,
                            "startTimeUnixNano": 1660539648581100000,
                            "endTimeUnixNano": 1660539648595000000,
                            "attributes": [
                                {
                                    "key": "http.method",
                                    "value": {
                                        "stringValue": "GET"
                                    }
                                },
                                {
                                    "key": "http.url",
                                    "value": {
                                        "stringValue": "http://django-max:8081/"
                                    }
                                },
                                {
                                    "key": "http.host",
                                    "value": {
                                        "stringValue": "django-max:8081"
                                    }
                                },
                                {
                                    "key": "http.scheme",
                                    "value": {
                                        "stringValue": "http"
                                    }
                                },
                                {
                                    "key": "http.target",
                                    "value": {
                                        "stringValue": "/"
                                    }
                                },
                                {
                                    "key": "http.user_agent",
                                    "value": {
                                        "stringValue": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36"
                                    }
                                },
                                {
                                    "key": "http.status_code",
                                    "value": {
                                        "intValue": 200
                                    }
                                }
                            ],
                            "droppedAttributesCount": 0,
                            "events": [],
                            "droppedEventsCount": 0,
                            "status": {
                                "code": 0
                            },
                            "links": [],
                            "droppedLinksCount": 0
                        }
                    ]
                }
            ]
        }
    ]
}

k6 GRPC

import grpc from 'k6/net/grpc';
import { check, sleep } from 'k6';

const client = new grpc.Client();
client.load(['definitions'], 'opentelemetry/proto/collector/trace/v1/trace_service.proto');
//client.load(['definitions'], 'hello.proto');
//client.load(['definitions'], 'otel.proto');
//client.load('otel.proto');

//const data0 = JSON.parse(open("./traces.txt"));
//const data0 = JSON.parse(open("./trace0.json"));
const data0 = JSON.parse(open("./agenda.json"));


const d = new Date().getTime()


export default () => {
  client.connect('lab003:4317', {
    // plaintext: false
      plaintext: true,
  });

  const data = { greeting: 'Bert' };

  data0.resourceSpans[0].scopeSpans[0].spans[0].startTimeUnixNano = d*1000000
  data0.resourceSpans[0].scopeSpans[0].spans[0].endTimeUnixNano = d*1000000
    //const traceparent = "00-9f85218a78664e1d3cba6b438e90bd67-eee5976fa7571dd3-01"
    const traceparent = "00-9f85218a78664e1d3cba6b438e90bd67-eee5976fa7500000-01"
  //data0.resourceSpans[0].scopeSpans[0].spans[0].traceId = traceparent
    let traceid = "c4d3ac8dffee87aec342d788d84679a1"
    let spanid = "9a98f4b1a7417410"
    let b64traceid = "YzRkM2FjOGRmZmVlODdhZWMzNDJkNzg4ZDg0Njc5YTEK"
    let b64spanid = "OWE5OGY0YjFhNzQxNzQxMAo="
    traceid = "kDMI7LTxLxTj220awNARJw=="
    spanid = "9ir6veJ4Hdw="
  data0.resourceSpans[0].scopeSpans[0].spans[0].traceId = traceid 
  data0.resourceSpans[0].scopeSpans[0].spans[0].spanId = spanid
  //data0.resourceSpans[0].scopeSpans[0].spans[0].traceId = "6c697d06c5eecac7fd62fbe4d02521e2"
  //data0.resourceSpans[0].scopeSpans[0].spans[0].spanId = "1c2be1cbaf16deec"
//    "TraceID": "c4d3ac8dffee87aec342d788d84679a1",
//    "SpanID": "9a98f4b1a7417410",
    //
    //"6c697d06c5eecac7fd62fbe4d02521e2",
    //"spanId": "1c2be1cbaf16deec",

  console.log(JSON.stringify(data0, null, 4))

  const response = client.invoke('/opentelemetry.proto.collector.trace.v1.TraceService/Export', data0);
  //const response = client.invoke('otel.TraceService/Export', data0);

  check(response, {
    'status is OK': (r) => {
        console.log('r', r)
        return r && r.status === grpc.StatusOK
    },
  });

  console.log(JSON.stringify(response.message));

  client.close();
  sleep(1);
};

ramp up iterations

export const options = {
    scenarios: {
        ramping_vus_scenario: {
            executor: "ramping-vus",
            startTime: '0s',
            stages: [{
                    target: 100,
                    duration: "15s"
                }
            ]
        },
    }
}

get proto

mkdir definitions
git clone https://github.com/open-telemetry/opentelemetry-proto.git
cp -r opentelemetry-proto/opentelemetry definitions/

K6 HTTP

import { sleep, check } from "k6";
import http from "k6/http";
import { htmlReport } from "https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js";

//export const options = {
//    scenarios: {
//        ramping_vus_scenario: {
//            executor: "ramping-vus",
//            startTime: '0s',
//            stages: [{
//                    target: 10,
//                    duration: "1s"
//                    //target: 100,
//                    //duration: "15s"
//                }
//            ]
//        },
//    }
//}

const d = new Date().getTime()

const payload = JSON.parse(open("./agenda.json"));


export default function () {

    payload.resourceSpans[0].scopeSpans[0].spans[0].startTimeUnixNano = d*1000000
    payload.resourceSpans[0].scopeSpans[0].spans[0].endTimeUnixNano = d*1000000

    let res = http.post("http://lab003:4318/v1/traces", JSON.stringify(payload), {
        headers: {
            "Content-Type": "application/json"
        }});
    console.log(res);
}

export function handleSummary(data) {
  return {
    "summary.html": htmlReport(data),
  };
}

Caveats

Trace/Span ids base64 encoded when used in proto-JSON representation #786

https://github.com/open-telemetry/opentelemetry-specification/issues/786

https://cryptii.com/pipes/base64-to-binary

kDMI7LTxLxTj220awNARJw==

import base64


import random                                     
                                                  
                                                  
def generate_span_id():                           
    return format(random.getrandbits(64), "016x") 
                                                  
def generate_trace_id():                          
    return format(random.getrandbits(128), "032x")


# from
# 903308ecb4f12f14e3db6d1ac0d01127
# to 
# kDMI7LTxLxTj220awNARJw==

if 1:

    a = "903308ecb4f12f14e3db6d1ac0d01127"

    bfh = bytes.fromhex(a)
    print(bfh)

    print("bytes.fromhex", bfh)

    print("baseencode", base64.b64encode(bfh)) # b'kDMI7LTxLxTj220awNARJw=='

# from
# kDMI7LTxLxTj220awNARJw==
# to 
# 903308ecb4f12f14e3db6d1ac0d01127

if 1:
    a = "kDMI7LTxLxTj220awNARJw=="

    benc = a.encode()

    print("benc", benc) # b'kDMI7LTxLxTj220awNARJw=='

    bdec = base64.b64decode(benc)

    print("bdec", bdec)

    print("tohex", bdec.hex()) # 903308ecb4f12f14e3db6d1ac0d01127

K6 gen traceid spanid

https://blog.abelotech.com/posts/generate-random-values-nodejs-javascript/

traceid = base64ArrayBuffer(crypto.randomBytes(16))
spanid = base64ArrayBuffer(crypto.randomBytes(8))  

k6

https://community.k6.io/t/base64-encode-arraybuffer/1152/2

var str = "testing"; // just for illustrative purposes, base64ArrayBuffer() seems to work for non-ASCII  values as well
var buffer = new ArrayBuffer(str.length);
var typedArr = new Uint8Array(buffer);
for (var i = 0; i < str.length; i++) {
    typedArr[i] = str.charCodeAt(i);
}
console.log(typedArr);
console.log(base64ArrayBuffer(buffer));

https://gist.github.com/jonleighton/958841#gistcomment-1953137

function base64ArrayBuffer(arrayBuffer) {
  var base64    = ''
  var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

  var bytes         = new Uint8Array(arrayBuffer)
  var byteLength    = bytes.byteLength
  var byteRemainder = byteLength % 3
  var mainLength    = byteLength - byteRemainder

  var a, b, c, d
  var chunk

  // Main loop deals with bytes in chunks of 3
  for (var i = 0; i < mainLength; i = i + 3) {
    // Combine the three bytes into a single integer
    chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2]

    // Use bitmasks to extract 6-bit segments from the triplet
    a = (chunk & 16515072) >> 18 // 16515072 = (2^6 - 1) << 18
    b = (chunk & 258048)   >> 12 // 258048   = (2^6 - 1) << 12
    c = (chunk & 4032)     >>  6 // 4032     = (2^6 - 1) << 6
    d = chunk & 63               // 63       = 2^6 - 1

    // Convert the raw binary segments to the appropriate ASCII encoding
    base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d]
  }

  // Deal with the remaining bytes and padding
  if (byteRemainder == 1) {
    chunk = bytes[mainLength]

    a = (chunk & 252) >> 2 // 252 = (2^6 - 1) << 2

    // Set the 4 least significant bits to zero
    b = (chunk & 3)   << 4 // 3   = 2^2 - 1

    base64 += encodings[a] + encodings[b] + '=='
  } else if (byteRemainder == 2) {
    chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1]

    a = (chunk & 64512) >> 10 // 64512 = (2^6 - 1) << 10
    b = (chunk & 1008)  >>  4 // 1008  = (2^6 - 1) << 4

    // Set the 2 least significant bits to zero
    c = (chunk & 15)    <<  2 // 15    = 2^4 - 1

    base64 += encodings[a] + encodings[b] + encodings[c] + '='
  }
  
  return base64
}