diff --git a/experiments/Gemfile b/experiments/Gemfile new file mode 100644 index 0000000..118b75d --- /dev/null +++ b/experiments/Gemfile @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +source 'https://rubygems.org/' + +ruby '~> 3.3' + +gem 'puma', git: 'https://github.com/dsalahutdinov/puma.git', branch: :add_preprocess_time +gem 'rack' +gem 'rackup' +gem 'yabeda-prometheus-mmap' +gem 'yabeda-puma-plugin', path: '/yabeda-puma-plugin' diff --git a/experiments/README.md b/experiments/README.md new file mode 100644 index 0000000..7a7b648 --- /dev/null +++ b/experiments/README.md @@ -0,0 +1,24 @@ +Navigate to experiments directory: +```sh +cd experiments +``` + +Setup environment +```sh +docker-compose run app bundle + +docker-compose up grafana promethes app +``` + +To run stress-test: + +```sh +docker-compose run k6 +``` + +Navagate to grafana http://localhost:3000 to "Puma experimental" dashboard. + +See results: + +![Results](docs/puma-requests_wait_time.png) + diff --git a/experiments/config.ru b/experiments/config.ru new file mode 100644 index 0000000..91f2838 --- /dev/null +++ b/experiments/config.ru @@ -0,0 +1,8 @@ +require 'rack' + +run do |_env| + # do some hard work + number = (0..9_999_990).to_a.map { |i| i * 2 }.sum + + [200, {}, ["Result #{number}"]] +end diff --git a/experiments/docker-compose.yml b/experiments/docker-compose.yml new file mode 100644 index 0000000..b57c4aa --- /dev/null +++ b/experiments/docker-compose.yml @@ -0,0 +1,84 @@ +x-app-resources: &x-app-resources + deploy: + resources: + reservations: + cpus: '1' + memory: 512M + limits: + cpus: '1' + memory: 512M + +x-app-service-template: &ruby-app + image: ruby:3.3 + environment: + HISTFILE: /app/tmp/.bash_history + BUNDLE_PATH: /usr/local/bundle + BUNDLE_CONFIG: /app/.bundle/config + prometheus_multiproc_dir: /tmp + stdin_open: true + tty: true + working_dir: /app +# tmpfs: +# - /tmp + +services: + app: + <<: + - *ruby-app + - *x-app-resources + command: bundle exec puma -t 16 -w 4 -C puma.rb + volumes: + - bundler_data:/usr/local/bundle + - .:/app:cached + - ..:/yabeda-puma-plugin + + k6: + image: grafana/k6 + command: run /app/k6.js + profiles: [manual-run] + volumes: + - .:/app:cached + sysctls: + - net.ipv4.tcp_tw_reuse=1 + + prometheus: + image: prom/prometheus:v2.17.1 + container_name: prometheus + volumes: + - ./prometheus:/etc/prometheus + - prometheus_data:/prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.path=/prometheus' + - '--web.console.libraries=/etc/prometheus/console_libraries' + - '--web.console.templates=/etc/prometheus/consoles' + - '--storage.tsdb.retention.time=200h' + - '--web.enable-lifecycle' + restart: unless-stopped + expose: + - 9090 + depends_on: + - app + + grafana: + image: grafana/grafana:11.2.2-security-01 + container_name: grafana + volumes: + - grafana_data:/var/lib/grafana + - ./grafana/provisioning:/etc/grafana/provisioning + environment: + - GF_SECURITY_ADMIN_USER=admin + - GF_SECURITY_ADMIN_PASSWORD=password + - GF_USERS_ALLOW_SIGN_UP=false + restart: unless-stopped + expose: + - 3000 + ports: + - "3000:3000" + depends_on: + - prometheus + +volumes: + bundler_data: + prometheus_data: + grafana_data: diff --git a/experiments/grafana/provisioning/dashboards/dashboard.yml b/experiments/grafana/provisioning/dashboards/dashboard.yml new file mode 100644 index 0000000..4c387ae --- /dev/null +++ b/experiments/grafana/provisioning/dashboards/dashboard.yml @@ -0,0 +1,12 @@ +apiVersion: 1 + +providers: + - name: 'Prometheus' + orgId: 1 + folder: '' + type: file + disableDeletion: false + editable: true + allowUiUpdates: true + options: + path: /etc/grafana/provisioning/dashboards diff --git a/experiments/grafana/provisioning/dashboards/puma.json b/experiments/grafana/provisioning/dashboards/puma.json new file mode 100644 index 0000000..507e7f0 --- /dev/null +++ b/experiments/grafana/provisioning/dashboards/puma.json @@ -0,0 +1,344 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 1, + "links": [], + "panels": [ + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "sum(rate(puma_requests_count[1m]))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Puma RPS", + "type": "timeseries" + }, + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 6 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "sum (puma_backlog)", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Backlog size", + "type": "timeseries" + }, + { + "datasource": { + "default": true, + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 13 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "disableTextWrap": false, + "editorMode": "code", + "expr": "sum(rate(puma_requests_wait_time[1m])) / sum(rate(puma_requests_count[1m]))", + "fullMetaSearch": false, + "includeNullMetadata": true, + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "AVG request wait_time", + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 39, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "2024-11-21T11:24:47.548Z", + "to": "2024-11-21T11:33:10.250Z" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Puma Experimental", + "uid": "ce4m4u99tleyoc", + "version": 2, + "weekStart": "" +} diff --git a/experiments/grafana/provisioning/datasources/datasource.yml b/experiments/grafana/provisioning/datasources/datasource.yml new file mode 100644 index 0000000..a08624a --- /dev/null +++ b/experiments/grafana/provisioning/datasources/datasource.yml @@ -0,0 +1,11 @@ +apiVersion: 1 + +datasources: + - name: Prometheus + type: prometheus + access: proxy + orgId: 1 + url: http://prometheus:9090 + basicAuth: false + isDefault: true + editable: true diff --git a/experiments/k6.js b/experiments/k6.js new file mode 100644 index 0000000..437d100 --- /dev/null +++ b/experiments/k6.js @@ -0,0 +1,17 @@ +import http from 'k6/http'; +export const options = { + stages: [ +// { duration: '30s', target: 1000 }, + { duration: '300s', target: 3000 }, +// { duration: '10s', target: 750 }, +// { duration: '10s', target: 500 }, +// { duration: '10s', target: 0 }, + ], +}; + + +export default function () { + for (let id = 1; id <= 100; id++) { + http.get("http://app:9292/"); + } +} diff --git a/experiments/prometheus/prometheus.yml b/experiments/prometheus/prometheus.yml new file mode 100644 index 0000000..1e0cf3c --- /dev/null +++ b/experiments/prometheus/prometheus.yml @@ -0,0 +1,19 @@ +global: + scrape_interval: 5s + evaluation_interval: 5s + + # Attach these labels to any time series or alerts when communicating with + # external systems (federation, remote storage, Alertmanager). + external_labels: + monitor: 'docker-host-alpha' + +# Load and evaluate rules in this file every 'evaluation_interval' seconds. +rule_files: + - "alert.rules" + +# A scrape configuration containing exactly one endpoint to scrape. +scrape_configs: + - job_name: 'client' + scrape_interval: 5s + static_configs: + - targets: ['app:9394'] diff --git a/experiments/puma-request_wait_time.png b/experiments/puma-request_wait_time.png new file mode 100644 index 0000000..4d6523b Binary files /dev/null and b/experiments/puma-request_wait_time.png differ diff --git a/experiments/puma.rb b/experiments/puma.rb new file mode 100644 index 0000000..6dcc82b --- /dev/null +++ b/experiments/puma.rb @@ -0,0 +1,10 @@ +require 'yabeda/prometheus/mmap' +require 'yabeda/puma/plugin' + +before_fork do + Yabeda.configure! +end + +activate_control_app 'unix:///var/run/pumactl.sock' +plugin :yabeda +plugin :yabeda_prometheus