diff --git a/README.md b/README.md index c6ea69c3..3bc41091 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,12 @@ Dashboard for Tarantool application and database server monitoring, based on [grafonnet](https://github.com/grafana/grafonnet-lib) library. Our pages on Grafana Official & community built dashboards: -[InfluxDB version](https://grafana.com/grafana/dashboards/12567), -[Prometheus version](https://grafana.com/grafana/dashboards/13054), -[InfluxDB TDG version](https://grafana.com/grafana/dashboards/16405), -[Prometheus TDG version](https://grafana.com/grafana/dashboards/16406). +- Tarantool Cartridge and Tarantool 1.10—2.x applications: + - [Prometheus](https://grafana.com/grafana/dashboards/13054), + - [InfluxDB](https://grafana.com/grafana/dashboards/12567); +- TDG + - [Prometheus](https://grafana.com/grafana/dashboards/16406), + - [InfluxDB](https://grafana.com/grafana/dashboards/16405). Refer to dashboard [documentation page](https://www.tarantool.io/en/doc/latest/book/monitoring/grafana_dashboard/) for prerequirements and installation guide. @@ -34,13 +36,9 @@ Refer to dashboard [documentation page](https://www.tarantool.io/en/doc/latest/b 2. To import a specific dashboard, choose one of the following options: - - paste the dashboard id (``12567`` for InfluxDB dashboard, ``13054`` for Prometheus dashboard, - ``16405`` for InfluxDB TDG dashboard, ``16406`` for Prometheus TDG dashboard), or - - paste a link to the dashboard ( - https://grafana.com/grafana/dashboards/12567 for InfluxDB dashboard, - https://grafana.com/grafana/dashboards/13054 for Prometheus dashboard, - https://grafana.com/grafana/dashboards/16405 for InfluxDB TDG dashboard, - https://grafana.com/grafana/dashboards/16406 for Prometheus TDG dashboard), or + - paste the dashboard id (for example, ``13054`` for Prometheus Tarantool Cartridge dashboard), or + - paste a link to the dashboard (for example, + https://grafana.com/grafana/dashboards/13054 for Prometheus Tarantool Cartridge dashboard), or - paste the dashboard JSON file contents, or - upload the dashboard JSON file. @@ -62,7 +60,7 @@ For guide on setting up your monitoring stack refer to [documentation page](http This repository provides preconfigured monitoring cluster with example Tarantool app and load generatior for local dashboard development and tests. ```bash -docker-compose up -d +docker compose up -d ``` will start 6 containers: Tarantool App, Tarantool Load Generator, Telegraf, InfluxDB, Prometheus and Grafana, which build cluster with two fully operational metrics datasources (InfluxDB and Prometheus), extracting metrics from Tarantool App example project. We recommend using the exact versions we use in experimental cluster (e.g. Grafana v8.1.5). @@ -79,7 +77,7 @@ See more setup tips in [documentation](https://www.tarantool.io/en/doc/latest/bo Start cluster with ```bash -docker-compose -f docker-compose.localapp.yml -p localapp-monitoring up -d +docker compose -f docker-compose.localapp.yml -p localapp-monitoring up -d ``` After start, Grafana UI will be available at [localhost:3000](http://localhost:3000/). You can also interact with Prometheus at [localhost:9090](http://localhost:9090/) and InfluxDB at [localhost:8086](http://localhost:8086/). diff --git a/doc/monitoring/grafana_dashboard.rst b/doc/monitoring/grafana_dashboard.rst index 2d69843b..c48bac92 100644 --- a/doc/monitoring/grafana_dashboard.rst +++ b/doc/monitoring/grafana_dashboard.rst @@ -4,14 +4,23 @@ Grafana dashboard =============================================================================== -Tarantool Grafana dashboard is available as part of -`Grafana Official & community built dashboards `_. -There's a version -`for Prometheus data source `_ -and one `for InfluxDB data source `_. -There are also separate dashboards for TDG applications: -`for Prometheus data source `_ -and `for InfluxDB data source `_. +Tarantool Grafana dashboards are available as part of +`Grafana Official & community built dashboards `_: + +.. container:: table + + .. list-table:: + :widths: 25 75 + :header-rows: 0 + + * Tarantool Cartridge and Tarantool 1.10—2.x applications: + - `Prometheus `_, + - `InfluxDB `_; + + * TDG: + - `Prometheus `_, + - `InfluxDB `_. + Tarantool Grafana dashboard is a ready for import template with basic memory, space operations, and HTTP load panels, based on default `metrics `_ package functionality. diff --git a/docker-compose.cartridge.yml b/docker-compose.cartridge.yml new file mode 100644 index 00000000..b7fb4f81 --- /dev/null +++ b/docker-compose.cartridge.yml @@ -0,0 +1,84 @@ +services: + app: + build: + context: ./example_cluster/cartridge_project + dockerfile: app.Dockerfile + networks: + tarantool_dashboard_dev: + ports: + - 13301:13301 + - 13302:13302 + - 13303:13303 + - 13304:13304 + - 13305:13305 + - 8081:8081 + - 8082:8082 + - 8083:8083 + - 8084:8084 + - 8085:8085 + + load_generator: + build: + context: ./example_cluster/cartridge_project + dockerfile: load.Dockerfile + networks: + tarantool_dashboard_dev: + + telegraf: + image: telegraf:1.13-alpine + networks: + tarantool_dashboard_dev: + volumes: + # configure telegraf to work out of the box + - ./example_cluster/telegraf/telegraf.conf:/etc/telegraf/telegraf.conf:ro + + influxdb: + image: influxdb:1.7-alpine + environment: + INFLUXDB_REPORTING_DISABLED: "true" + INFLUXDB_DB: "metrics" + INFLUXDB_ADMIN_USER: "admin" + INFLUXDB_ADMIN_PASSWORD: "admin" + INFLUXDB_USER: "telegraf" + INFLUXDB_USER_PASSWORD: "telegraf" + INFLUXDB_HTTP_AUTH_ENABLED: "true" + networks: + tarantool_dashboard_dev: + ports: + - 8086:8086 + + prometheus: + image: prom/prometheus:v2.17.2 + networks: + tarantool_dashboard_dev: + ports: + - 9090:9090 + volumes: + - ./example_cluster/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml + - ./example_cluster/prometheus/alerts.yml:/etc/prometheus/alerts.yml + + grafana: + image: grafana/grafana:8.1.3 + environment: + GF_SECURITY_DISABLE_INITIAL_ADMIN_CREATION: "true" + GF_AUTH_ANONYMOUS_ENABLED: "true" + GF_AUTH_ANONYMOUS_ORG_ROLE: "Admin" + GF_AUTH_DISABLE_SIGNOUT_MENU: "true" + GF_AUTH_DISABLE_LOGIN_FORM: "true" + networks: + tarantool_dashboard_dev: + ports: + - 3000:3000 + volumes: + - ./example_cluster/grafana/provisioning:/etc/grafana/provisioning + # Map to different folders to prevent name collision. + - ./tests/Prometheus/dashboard_compiled.json:/usr/lib/dashboards/Prometheus-common/dashboard.json + - ./tests/Prometheus/dashboard_static_compiled.json:/usr/lib/dashboards/Prometheus-static/dashboard.json + - ./tests/Prometheus/dashboard_static_with_instance_variable_compiled.json:/usr/lib/dashboards/Prometheus-static-var/dashboard.json + - ./tests/InfluxDB/dashboard_compiled.json:/usr/lib/dashboards/InfluxDB-common/dashboard.json + - ./tests/InfluxDB/dashboard_static_compiled.json:/usr/lib/dashboards/InfluxDB-static/dashboard.json + - ./tests/InfluxDB/dashboard_static_with_instance_variable_compiled.json:/usr/lib/dashboards/InfluxDB-static-var/dashboard.json + +networks: + tarantool_dashboard_dev: + driver: bridge diff --git a/docker-compose.localapp.yml b/docker-compose.localapp.yml index 0e141985..f903de27 100644 --- a/docker-compose.localapp.yml +++ b/docker-compose.localapp.yml @@ -1,4 +1,3 @@ -version: '3' services: telegraf: image: telegraf:1.13-alpine diff --git a/docker-compose.tdg.yml b/docker-compose.tdg.yml index 237cfa0a..cfce863c 100644 --- a/docker-compose.tdg.yml +++ b/docker-compose.tdg.yml @@ -1,4 +1,3 @@ -version: '3' services: tdg: image: tdg diff --git a/docker-compose.yml b/docker-compose.yml index 3ebabb76..683d81f2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,7 @@ -version: '3' services: app: build: - context: ./example_cluster/project + context: ./example_cluster/tarantool3_project dockerfile: app.Dockerfile networks: tarantool_dashboard_dev: @@ -20,7 +19,7 @@ services: load_generator: build: - context: ./example_cluster/project + context: ./example_cluster/tarantool3_project dockerfile: load.Dockerfile networks: tarantool_dashboard_dev: diff --git a/example_cluster/project/.cartridge.yml b/example_cluster/cartridge_project/.cartridge.yml similarity index 100% rename from example_cluster/project/.cartridge.yml rename to example_cluster/cartridge_project/.cartridge.yml diff --git a/example_cluster/project/.editorconfig b/example_cluster/cartridge_project/.editorconfig similarity index 100% rename from example_cluster/project/.editorconfig rename to example_cluster/cartridge_project/.editorconfig diff --git a/example_cluster/project/app.Dockerfile b/example_cluster/cartridge_project/app.Dockerfile similarity index 100% rename from example_cluster/project/app.Dockerfile rename to example_cluster/cartridge_project/app.Dockerfile diff --git a/example_cluster/project/app/roles/custom.lua b/example_cluster/cartridge_project/app/roles/custom.lua similarity index 100% rename from example_cluster/project/app/roles/custom.lua rename to example_cluster/cartridge_project/app/roles/custom.lua diff --git a/example_cluster/project/app/roles/router.lua b/example_cluster/cartridge_project/app/roles/router.lua similarity index 100% rename from example_cluster/project/app/roles/router.lua rename to example_cluster/cartridge_project/app/roles/router.lua diff --git a/example_cluster/project/app/roles/storage.lua b/example_cluster/cartridge_project/app/roles/storage.lua similarity index 100% rename from example_cluster/project/app/roles/storage.lua rename to example_cluster/cartridge_project/app/roles/storage.lua diff --git a/example_cluster/project/project-scm-1.rockspec b/example_cluster/cartridge_project/cartridge_project-scm-1.rockspec similarity index 90% rename from example_cluster/project/project-scm-1.rockspec rename to example_cluster/cartridge_project/cartridge_project-scm-1.rockspec index 836846e4..ac469294 100644 --- a/example_cluster/project/project-scm-1.rockspec +++ b/example_cluster/cartridge_project/cartridge_project-scm-1.rockspec @@ -1,4 +1,4 @@ -package = 'project' +package = 'cartridge_project' version = 'scm-1' source = { url = '/dev/null', diff --git a/example_cluster/project/cookie.lua b/example_cluster/cartridge_project/cookie.lua similarity index 100% rename from example_cluster/project/cookie.lua rename to example_cluster/cartridge_project/cookie.lua diff --git a/example_cluster/project/generate_load.lua b/example_cluster/cartridge_project/generate_load.lua similarity index 100% rename from example_cluster/project/generate_load.lua rename to example_cluster/cartridge_project/generate_load.lua diff --git a/example_cluster/project/init.lua b/example_cluster/cartridge_project/init.lua similarity index 100% rename from example_cluster/project/init.lua rename to example_cluster/cartridge_project/init.lua diff --git a/example_cluster/project/instances.yml b/example_cluster/cartridge_project/instances.yml similarity index 57% rename from example_cluster/project/instances.yml rename to example_cluster/cartridge_project/instances.yml index 62a114e6..04774556 100644 --- a/example_cluster/project/instances.yml +++ b/example_cluster/cartridge_project/instances.yml @@ -1,20 +1,20 @@ --- -project.tnt_router: +cartridge_project.tnt_router: advertise_uri: localhost:13301 http_port: 8081 -project.tnt_storage_1_master: +cartridge_project.tnt_storage_1_master: advertise_uri: localhost:13302 http_port: 8082 -project.tnt_storage_1_replica: +cartridge_project.tnt_storage_1_replica: advertise_uri: localhost:13303 http_port: 8083 -project.tnt_storage_2_master: +cartridge_project.tnt_storage_2_master: advertise_uri: localhost:13304 http_port: 8084 -project.tnt_storage_2_replica: +cartridge_project.tnt_storage_2_replica: advertise_uri: localhost:13305 http_port: 8085 diff --git a/example_cluster/project/load.Dockerfile b/example_cluster/cartridge_project/load.Dockerfile similarity index 100% rename from example_cluster/project/load.Dockerfile rename to example_cluster/cartridge_project/load.Dockerfile diff --git a/example_cluster/project/replicasets.yml b/example_cluster/cartridge_project/replicasets.yml similarity index 100% rename from example_cluster/project/replicasets.yml rename to example_cluster/cartridge_project/replicasets.yml diff --git a/example_cluster/tarantool3_project/app.Dockerfile b/example_cluster/tarantool3_project/app.Dockerfile new file mode 100644 index 00000000..89b5a4e2 --- /dev/null +++ b/example_cluster/tarantool3_project/app.Dockerfile @@ -0,0 +1,28 @@ +FROM golang:1.22-bullseye + +WORKDIR /app + +RUN DEBIAN_FRONTEND=noninteractive apt update +RUN DEBIAN_FRONTEND=noninteractive apt install -y git \ + cmake \ + make \ + gcc \ + g++ \ + unzip \ + curl +COPY . . +RUN mkdir -p tmp + +RUN curl -L https://tarantool.io/release/3/installer.sh | bash +RUN DEBIAN_FRONTEND=noninteractive apt install -y tarantool tarantool-dev tt + +RUN tt init +# Need tt log +RUN DEBIAN_FRONTEND=noninteractive apt install -y git patch +RUN git clone https://github.com/magefile/mage && \ + cd mage && \ + go run bootstrap.go +RUN tt install tt master + +RUN tt rocks make +ENTRYPOINT tt start && tt log -f diff --git a/example_cluster/tarantool3_project/config.yaml b/example_cluster/tarantool3_project/config.yaml new file mode 100644 index 00000000..57476ffa --- /dev/null +++ b/example_cluster/tarantool3_project/config.yaml @@ -0,0 +1,122 @@ +credentials: + users: + admin: + password: 'secret' + roles: [super] + replicator: + password: 'secret' + roles: [replication] + storage: + password: 'secret' + roles: [sharding] + +sharding: + bucket_count: 3000 + +groups: + routers: + sharding: + roles: [router] + app: + module: router + roles: + - roles.crud-router + - roles.pseudoapp + roles_cfg: + roles.crud-router: + stats: true + stats_driver: metrics + stats_quantiles: true + replicasets: + routers: + instances: + router: + roles_cfg: + roles.pseudoapp: + listen: 0.0.0.0:8081 + iproto: + listen: + - uri: 0.0.0.0:13301 + advertise: + peer: + login: replicator + uri: localhost:13301 + sharding: + login: storage + uri: localhost:13301 + client: localhost:13301 + storages: + sharding: + roles: [storage] + roles: + - roles.crud-storage + - roles.pseudoapp + replication: + failover: manual + replicasets: + storages_1: + leader: storage_1_master + instances: + storage_1_master: + roles_cfg: + roles.pseudoapp: + listen: 0.0.0.0:8082 + iproto: + listen: + - uri: 0.0.0.0:13302 + advertise: + peer: + login: replicator + uri: localhost:13302 + sharding: + login: storage + uri: localhost:13302 + client: localhost:13302 + storage_1_replica: + roles_cfg: + roles.pseudoapp: + listen: 0.0.0.0:8083 + iproto: + listen: + - uri: 0.0.0.0:13303 + advertise: + peer: + login: replicator + uri: localhost:13303 + sharding: + login: storage + uri: localhost:13303 + client: localhost:13303 + storages_2: + leader: storage_2_master + instances: + storage_2_master: + roles_cfg: + roles.pseudoapp: + listen: 0.0.0.0:8084 + iproto: + listen: + - uri: 0.0.0.0:13304 + advertise: + peer: + login: replicator + uri: localhost:13304 + sharding: + login: storage + uri: localhost:13304 + client: localhost:13304 + storage_2_replica: + roles_cfg: + roles.pseudoapp: + listen: 0.0.0.0:8085 + iproto: + listen: + - uri: 0.0.0.0:13305 + advertise: + peer: + login: replicator + uri: localhost:13305 + sharding: + login: storage + uri: localhost:13305 + client: localhost:13305 diff --git a/example_cluster/tarantool3_project/generate_load.lua b/example_cluster/tarantool3_project/generate_load.lua new file mode 100644 index 00000000..8fbecb6b --- /dev/null +++ b/example_cluster/tarantool3_project/generate_load.lua @@ -0,0 +1,713 @@ +local log = require('log') +local fiber = require('fiber') +local net_box = require('net.box') + +local APP_HOST = os.getenv('APP_HOST') or 'app' + + +local http_client = require('http.client').new({max_connections = 5}) + +local function http_request(route, method, count) + if count <= 0 then + return + end + + for _ = 1, count do + http_client:request(method, route) + end +end + +local function generate_http_load(name, instance) + if instance.http_uri == nil then + return + end + + local count_200, count_400 + + if name:match('router') ~= nil then + count_200 = math.random(5, 10) + count_400 = math.random(1, 2) + else + if name:match('replica') ~= nil then + count_200 = math.random(1, 3) + count_400 = math.random(0, 1) + else + count_200 = math.random(2, 5) + count_400 = math.random(0, 2) + end + end + + local count_500 = math.random(0, 1) + + http_request(('%s/hello'):format(instance.http_uri), 'GET', count_200) + http_request(('%s/hell0'):format(instance.http_uri), 'GET', count_400) + http_request(('%s/goodbye'):format(instance.http_uri), 'POST', count_500) +end + + +-- Space operations constants +local SELECT = 'select' +local INSERT = 'insert' +local UPDATE = 'update' +local UPSERT = 'upsert' +local REPLACE = 'replace' +local DELETE = 'delete' +local COMMIT = 'commit' +local ROLLBACK = 'rollback' + +local last_key = 1 + +local charset = {} -- [0-9a-zA-Z] +for c = 48, 57 do table.insert(charset, string.char(c)) end +for c = 65, 90 do table.insert(charset, string.char(c)) end +for c = 97, 122 do table.insert(charset, string.char(c)) end + +local function random_string(length) + if not length or length <= 0 then return '' end + math.randomseed(os.clock()^5) + return random_string(length - 1) .. charset[math.random(1, #charset)] +end + +local function space_operations(instance, operation, count) + if count <= 0 then + return + end + + local spaces = { + instance.net_box.space.MY_SPACE, + instance.net_box.space.MY_VINYL_SPACE + } + + for _, space in ipairs(spaces) do + if operation == SELECT then + for _ = 1, count do + space:select({}, { limit = 1 }) + end + + elseif operation == INSERT then + for _ = 1, count do + space:insert{ last_key, random_string(5) } + last_key = last_key + 1 + end + + elseif operation == UPDATE then + for _ = 1, count do + local tuple = space:select({}, { limit = 1 })[1] + if tuple == nil then return end + space:update(tuple[1], {{ '=', 2, random_string(5) }}) + end + + elseif operation == UPSERT then + for _ = 1, count do + local tuple = space:select({}, { limit = 1 })[1] + if tuple == nil then return end + space:upsert(tuple, {{ '=', 2, random_string(5) }}) + end + + elseif operation == REPLACE then + for _ = 1, count do + local tuple = space:select({}, { limit = 1 })[1] + if tuple == nil then return end + space:replace{ tuple[1], random_string(5) } + end + + elseif operation == DELETE then + for _ = 1, count do + local tuple = space:select({}, { limit = 1 })[1] + if tuple == nil then return end + space:delete{ tuple[1] } + end + + elseif operation == COMMIT then + for _ = 1, count do + local s = instance.net_box:new_stream() + s:begin() + s:commit() + end + + elseif operation == ROLLBACK then + for _ = 1, count do + local s = instance.net_box:new_stream() + s:begin() + s:rollback() + end + end + end +end + +local function generate_space_load(name, instance) + local space_load = {} + + if name:match('storage') ~= nil then + if name:match('replica') ~= nil then + space_load[SELECT] = math.random(3, 5) + else + space_load[INSERT] = math.random(5, 10) + space_load[SELECT] = math.random(10, 20) + space_load[UPDATE] = math.random(5, 10) + space_load[UPSERT] = math.random(5, 10) + space_load[REPLACE] = math.random(5, 10) + space_load[DELETE] = math.random(1, 2) + space_load[COMMIT] = math.random(1, 5) + space_load[ROLLBACK] = math.random(0, 2) + end + else + space_load[INSERT] = math.random(1, 3) + space_load[UPDATE] = math.random(1, 3) + end + + for operation, count in pairs(space_load) do + space_operations(instance, operation, count) + end +end + + +local function generate_operations_load(name, instance) + if name:match('storage') ~= nil then + instance.net_box:eval([[return box.execute("VALUES ('hello');")]]) + + if math.random(0, 100) == 25 then + -- duplicate key error and read-only instance error + pcall(instance.net_box.space.MY_SPACE.insert, instance.net_box.space.MY_SPACE, {0, random_string(5)}) + end + end + + if name:match('router') ~= nil then + instance.net_box:eval('return true') + end +end + +local LEN = 'LEN' +local GET = 'GET' +local BORDERS = 'BORDERS' +local COUNT = 'COUNT' +local TRUNCATE = 'TRUNCATE' +local BIG_SELECT = 'BIG_SELECT' +local INSERT_MANY = 'INSERT_MANY' +local REPLACE_MANY = 'REPLACE_MANY' +local UPSERT_MANY = 'UPSERT_MANY' +local truncate_inited = false +local crud_index = 1 +local crud_space_1 = 'customers' +local crud_space_2 = 'clients' +local crud_bad_space = 'non_existing_space' + +local function crud_operations_ok(instance, operation, count, space_name) + if count <= 0 then + return + end + + if operation == SELECT then + for _ = 1, count do + local _, err = instance.net_box:call('crud.select', { + space_name, {{ '==', 'id', crud_index}} + }) + + if err ~= nil then + log.error(err) + end + end + + elseif operation == BIG_SELECT then + for _ = 1, count do + -- Setup bucket_id to disable map reduce. + local _, err = instance.net_box:call('crud.select', { + space_name, {{ '<=', 'id', crud_index}}, { bucket_id = 1, first = 10 } + }) + + if err ~= nil then + log.error(err) + end + end + + elseif operation == INSERT then + for _ = 1, count do + local _, err = instance.net_box:call('crud.insert', { + space_name, { + crud_index, + box.NULL, + random_string(8), + math.random(20, 30), + }, + }) + crud_index = crud_index + 1 + + if err ~= nil then + log.error(err) + end + end + + elseif operation == INSERT_MANY then + for _ = 1, count do + local _, err = instance.net_box:call('crud.insert_many', { + space_name, { + { + crud_index, + box.NULL, + random_string(8), + math.random(20, 30), + }, + { + crud_index + 1, + box.NULL, + random_string(8), + math.random(20, 30), + }, + { + crud_index + 2, + box.NULL, + random_string(8), + math.random(20, 30), + }, + }, + }) + crud_index = crud_index + 3 + + if err ~= nil then + log.error(err) + end + end + + elseif operation == UPDATE then + -- Will also generate non-null map reduces + local res, err = instance.net_box:call('crud.select', { + space_name, {{ '<=', 'id', crud_index }}, { first = 1 } + }) + + if err ~= nil then + log.error(err) + return + end + + if res.rows[1] == nil then + log.warn('No record for update') + return + end + + for _ = 1, count do + local _, err = instance.net_box:call('crud.update', { + space_name, + res.rows[1][1], + {{ '=', 3, random_string(5) }}, + }) + + if err ~= nil then + log.error(err) + end + end + + elseif operation == UPSERT then + for _ = 1, count do + local _, err = instance.net_box:call('crud.upsert', { + space_name, { + crud_index, + box.NULL, + random_string(8), + math.random(20, 30), + }, + {} + }) + crud_index = crud_index + 1 + + if err ~= nil then + log.error(err) + end + end + + elseif operation == UPSERT_MANY then + for _ = 1, count do + local _, err = instance.net_box:call('crud.upsert_many', { + space_name, { + {{ + crud_index, + box.NULL, + random_string(8), + math.random(20, 30), + }, {}}, + {{ + crud_index + 1, + box.NULL, + random_string(8), + math.random(20, 30), + }, {}}, + {{ + crud_index + 2, + box.NULL, + random_string(8), + math.random(20, 30), + }, {}}, + }, + }) + crud_index = crud_index + 3 + + if err ~= nil then + log.error(err) + end + end + + elseif operation == REPLACE then + for _ = 1, count do + local _, err = instance.net_box:call('crud.replace', { + space_name, { + crud_index, + box.NULL, + random_string(8), + math.random(20, 30), + }, + }) + crud_index = crud_index + 1 + + if err ~= nil then + log.error(err) + end + end + + elseif operation == REPLACE_MANY then + for _ = 1, count do + local _, err = instance.net_box:call('crud.replace_many', { + space_name, { + { + crud_index, + box.NULL, + random_string(8), + math.random(20, 30), + }, + { + crud_index + 1, + box.NULL, + random_string(8), + math.random(20, 30), + }, + { + crud_index + 2, + box.NULL, + random_string(8), + math.random(20, 30), + }, + }, + }) + crud_index = crud_index + 3 + + if err ~= nil then + log.error(err) + end + end + + elseif operation == DELETE then + for _ = 1, count do + -- Will also generate non-null map reduces and tuple lookup. + local res, err = instance.net_box:call('crud.select', { + space_name, {{ '<=', 'id', crud_index }}, { first = 1 } + }) + + if err ~= nil then + log.error(err) + return + end + + if res.rows[1] == nil then + log.warn('No record for delete') + return + end + + local _, err = instance.net_box:call('crud.delete', {space_name, {res.rows[1][1]}}) + + if err ~= nil then + log.error(err) + end + end + + elseif operation == COUNT then + for _ = 1, count do + local _, err = instance.net_box:call('crud.count', { + space_name, {{ '==', 'id', crud_index}} + }) + + if err ~= nil then + log.error(err) + end + end + + elseif operation == GET then + for _ = 1, count do + local _, err = instance.net_box:call('crud.get', { + space_name, crud_index, + }) + + if err ~= nil then + log.error(err) + end + end + + elseif operation == BORDERS then + for _ = 1, count do + local _, err = instance.net_box:call('crud.min', {space_name, 'id_index'}) + + if err ~= nil then + log.error(err) + end + end + + elseif operation == LEN then + for _ = 1, count do + local _, err = instance.net_box:call('crud.len', {space_name}) + + if err ~= nil then + log.error(err) + end + end + + elseif operation == TRUNCATE then + for _ = 1, count do + local _, err = instance.net_box:call('crud.truncate', {space_name}) + + if err ~= nil then + log.error(err) + end + end + end +end + +local function crud_operations_err(instance, operation, count) + if count <= 0 then + return + end + + if operation == SELECT then + for _ = 1, count do + instance.net_box:call('crud.select', { + crud_bad_space, {{ '==', 'id', crud_index}} + }) + end + + elseif operation == INSERT then + for _ = 1, count do + instance.net_box:call('crud.insert', {crud_bad_space, {}}) + end + + elseif operation == INSERT_MANY then + for _ = 1, count do + instance.net_box:call('crud.insert_many', {crud_bad_space, {{}}}) + end + + elseif operation == UPDATE then + for _ = 1, count do + instance.net_box:call('crud.update', { + crud_bad_space, 1, {{ '==', 3, random_string(5) }}, + }) + end + + elseif operation == UPSERT then + for _ = 1, count do + instance.net_box:call('crud.upsert', { + crud_bad_space, {}, {{ '==', 3, random_string(5) }} + }) + end + + elseif operation == UPSERT_MANY then + for _ = 1, count do + instance.net_box:call('crud.upsert_many', { + crud_bad_space, {{}, {{ '==', 3, random_string(5) }}} + }) + end + + elseif operation == REPLACE then + for _ = 1, count do + instance.net_box:call('crud.replace', {crud_bad_space, {}}) + end + + elseif operation == REPLACE_MANY then + for _ = 1, count do + instance.net_box:call('crud.replace_many', {crud_bad_space, {{}}}) + end + + elseif operation == DELETE then + for _ = 1, count do + instance.net_box:call('crud.delete', {crud_bad_space, 1}) + end + + elseif operation == COUNT then + for _ = 1, count do + instance.net_box:call('crud.count', { + crud_bad_space, {{ '==', 'id', crud_index}} + }) + end + + elseif operation == GET then + for _ = 1, count do + instance.net_box:call('crud.get', {crud_bad_space, 1}) + end + + elseif operation == BORDERS then + for _ = 1, count do + instance.net_box:call('crud.min', {crud_bad_space, 'id_index'}) + end + + elseif operation == LEN then + for _ = 1, count do + instance.net_box:call('crud.len', {crud_bad_space}) + end + + elseif operation == TRUNCATE then + for _ = 1, count do + instance.net_box:call('crud.truncate', {crud_bad_space}) + end + end +end + +local function generate_crud_load(name, instance) + local space_load_ok_1 = {} + local space_load_ok_2 = {} + local space_load_err = {} + + if name:match('router') ~= nil then + space_load_ok_1[INSERT] = math.random(3, 5) + space_load_ok_1[INSERT_MANY] = math.random(1, 2) + space_load_ok_1[SELECT] = math.random(5, 10) + space_load_ok_1[BIG_SELECT] = math.random(1, 3) + space_load_ok_1[GET] = math.random(5, 10) + space_load_ok_1[UPDATE] = math.random(2, 3) + space_load_ok_1[REPLACE] = math.random(2, 3) + space_load_ok_1[REPLACE_MANY] = math.random(1, 2) + space_load_ok_1[UPSERT] = math.random(1, 2) + space_load_ok_1[UPSERT_MANY] = math.random(1, 2) + space_load_ok_1[DELETE] = math.random(1, 2) + space_load_ok_1[LEN] = math.random(0, 1) + space_load_ok_1[COUNT] = math.random(1, 2) + space_load_ok_1[BORDERS] = math.random(1, 2) + + space_load_ok_2[INSERT] = math.random(6, 8) + space_load_ok_2[INSERT_MANY] = math.random(1, 2) + space_load_ok_2[SELECT] = math.random(2, 4) + space_load_ok_2[BIG_SELECT] = math.random(2, 4) + space_load_ok_2[GET] = math.random(5, 10) + space_load_ok_2[UPDATE] = math.random(2, 3) + space_load_ok_2[REPLACE] = math.random(2, 3) + space_load_ok_2[REPLACE_MANY] = math.random(1, 2) + space_load_ok_2[UPSERT] = math.random(1, 2) + space_load_ok_2[UPSERT_MANY] = math.random(1, 2) + space_load_ok_2[DELETE] = math.random(1, 2) + space_load_ok_2[LEN] = math.random(0, 1) + space_load_ok_2[COUNT] = math.random(1, 2) + space_load_ok_2[BORDERS] = math.random(1, 2) + + space_load_err[INSERT] = math.random(1, 3) + space_load_err[INSERT_MANY] = math.random(0, 1) + space_load_err[SELECT] = math.random(2, 5) + space_load_err[GET] = math.random(2, 5) + space_load_err[UPDATE] = math.random(0, 1) + space_load_err[REPLACE] = math.random(0, 1) + space_load_err[REPLACE_MANY] = math.random(0, 1) + space_load_err[UPSERT] = math.random(0, 1) + space_load_err[UPSERT_MANY] = math.random(0, 1) + space_load_err[DELETE] = math.random(0, 1) + space_load_err[LEN] = math.random(0, 1) + space_load_err[COUNT] = math.random(0, 1) + space_load_err[BORDERS] = math.random(1, 2) + + if truncate_inited then + space_load_ok_1[TRUNCATE] = math.floor(math.random(1, 1000) / 1000) + space_load_ok_2[TRUNCATE] = math.floor(math.random(1, 1000) / 1000) + space_load_err[TRUNCATE] = math.floor(math.random(1, 1000) / 1000) + else + space_load_ok_1[TRUNCATE] = 1 + space_load_ok_2[TRUNCATE] = 1 + space_load_err[TRUNCATE] = 1 + truncate_inited = true + end + else + return + end + + for operation, count in pairs(space_load_ok_1) do + crud_operations_ok(instance, operation, count, crud_space_1) + end + + for operation, count in pairs(space_load_ok_2) do + crud_operations_ok(instance, operation, count, crud_space_2) + end + + for operation, count in pairs(space_load_err) do + crud_operations_err(instance, operation, count) + end +end + +local instances = { + ['router'] = { + http_port = 8081, + advertise_uri = 'localhost:13301', + }, + ['storage_1_master'] = { + http_port = 8082, + advertise_uri = 'localhost:13302', + }, + ['storage_1_replica'] = { + http_port = 8083, + advertise_uri = 'localhost:13303', + }, + ['storage_2_master'] = { + http_port = 8084, + advertise_uri = 'localhost:13304', + }, + ['storage_2_replica'] = { + http_port = 8085, + advertise_uri = 'localhost:13305', + }, +} + +for _, instance in pairs(instances) do + if instance.http_port ~= nil then + instance.http_uri = ('%s:%d'):format(APP_HOST, instance.http_port) + end + + instance.advertise_uri = instance.advertise_uri:gsub('localhost', APP_HOST) +end + + +fiber.sleep(5) + +for _, instance in pairs(instances) do + instance.net_box = net_box.connect( + instance.advertise_uri, + { user = 'admin', password = 'secret', reconnect_after = 5, wait_connected = 10 } + ) + pcall(instance.net_box.eval, instance.net_box, 'return box.space.MY_SPACE:truncate()') +end + +local load_generators = { + generate_http_load, + generate_space_load, + generate_operations_load, + generate_crud_load, +} + +for name, instance in pairs(instances) do + if name:match('router') ~= nil then + local spaces = {instance.net_box.space.MY_SPACE, instance.net_box.space.MY_VINYL_SPACE} + for _, space in ipairs(spaces) do + local task_name = name .. "_" .. space.name + local eval_str = string.format([[ + local expirationd = require('expirationd') + local half_true = function() return math.random(0, 1) == 0 and true or false end + local always_true = function() return true end + expirationd.start("%s", %d, half_true, { + process_expired_tuple = always_true, + force = true }) + ]], task_name, space.id) + instance.net_box:eval(eval_str) + end + end +end + +while true do + for name, instance in pairs(instances) do + for _, load_generator in ipairs(load_generators) do + local _, err = pcall(load_generator, name, instance) + if err ~= nil then + log.error(err) + fiber.sleep(1) + end + fiber.yield() + end + end +end diff --git a/example_cluster/tarantool3_project/instances.yaml b/example_cluster/tarantool3_project/instances.yaml new file mode 100644 index 00000000..9f32855d --- /dev/null +++ b/example_cluster/tarantool3_project/instances.yaml @@ -0,0 +1,6 @@ +--- +router: +storage_1_master: +storage_1_replica: +storage_2_master: +storage_2_replica: diff --git a/example_cluster/tarantool3_project/load.Dockerfile b/example_cluster/tarantool3_project/load.Dockerfile new file mode 100644 index 00000000..4756edbb --- /dev/null +++ b/example_cluster/tarantool3_project/load.Dockerfile @@ -0,0 +1,19 @@ +FROM ubuntu:22.04 + +WORKDIR /app + +RUN DEBIAN_FRONTEND=noninteractive apt update +RUN DEBIAN_FRONTEND=noninteractive apt install -y git \ + cmake \ + make \ + gcc \ + g++ \ + unzip \ + curl +COPY . . +RUN mkdir -p tmp + +RUN curl -L https://tarantool.io/release/3/installer.sh | bash +RUN DEBIAN_FRONTEND=noninteractive apt install -y tarantool + +ENTRYPOINT tarantool ./generate_load.lua diff --git a/example_cluster/tarantool3_project/roles/pseudoapp.lua b/example_cluster/tarantool3_project/roles/pseudoapp.lua new file mode 100644 index 00000000..4b302f67 --- /dev/null +++ b/example_cluster/tarantool3_project/roles/pseudoapp.lua @@ -0,0 +1,143 @@ +local fiber = require('fiber') +local metrics = require('metrics') +local json_exporter = require('metrics.plugins.json') +local prometheus_exporter = require('metrics.plugins.prometheus') + +local http_server = require('http.server') + +local httpd + +local function parse(listen) + local parts = listen:split(':') + return parts[1], tonumber(parts[2]) +end + +local function validate(cfg) + assert(type(cfg.listen) == 'string') + local host, port = parse(cfg.listen) + assert(type(host) == 'string') + assert(host ~= '') + assert(port ~= 0) +end + +local function apply(cfg) + if httpd ~= nil then + return + end + + fiber.leak_backtrace_enable() + + local host, port = parse(cfg.listen) + httpd = http_server.new(host, port) + + httpd:route( + { method = 'GET', path = '/metrics/prometheus' }, + prometheus_exporter.collect_http + ) + + httpd:route( + { method = 'GET', path = '/metrics/json' }, + function(request) + return request:render({ text = json_exporter.export() }) + end + ) + + local http_middleware = metrics.http_middleware + http_middleware.configure_default_collector('summary') + + httpd:route( + { method = 'GET', path = '/hello' }, + http_middleware.v1( + function() + fiber.sleep(0.02) + return { status = 200, body = 'Hello world!' } + end + ) + ) + httpd:route( + { method = 'GET', path = '/hell0' }, + http_middleware.v1( + function() + fiber.sleep(0.01) + return { status = 400, body = 'Hell0 world!' } + end + ) + ) + httpd:route( + { method = 'POST', path = '/goodbye' }, + http_middleware.v1( + function() + fiber.sleep(0.005) + return { status = 500, body = 'Goodbye cruel world!' } + end + ) + ) + + httpd:start() + + box.watch('box.status', function() + if box.info.ro then + return + end + + box.ctl.promote() + + local sp = box.schema.space.create('MY_SPACE', { if_not_exists = true }) + sp:format({ + { name = 'key', type = 'number', is_nullable = false }, + { name = 'value', type = 'string', is_nullable = false }, + }) + sp:create_index('pk', { parts = { 'key' }, if_not_exists = true }) + + local v_sp = box.schema.space.create('MY_VINYL_SPACE', { if_not_exists = true, engine = 'vinyl' }) + v_sp:format({ + { name = 'key', type = 'number', is_nullable = false }, + { name = 'value', type = 'string', is_nullable = false }, + }) + v_sp:create_index('pk', { parts = { 'key' }, if_not_exists = true }) + + for _, space_name in ipairs({'customers', 'clients'}) do + local space = box.schema.space.create(space_name, { + format = { + {name = 'id', type = 'unsigned'}, + {name = 'bucket_id', type = 'unsigned'}, + {name = 'name', type = 'string'}, + {name = 'age', type = 'number'}, + }, + if_not_exists = true, + engine = 'memtx', + is_sync = true, + }) + -- primary index + space:create_index('id_index', { + parts = {{field = 'id'}}, + if_not_exists = true, + }) + space:create_index('bucket_id', { + parts = { {field = 'bucket_id'} }, + unique = false, + if_not_exists = true, + }) + space:create_index('name_index', { + parts = { {field = 'name'} }, + unique = false, + if_not_exists = true, + }) + end + end) + + rawset(_G, 'include_vinyl_count', true) +end + +local function stop() + if httpd ~= nil then + httpd:stop() + httpd = nil + end +end + +return { + validate = validate, + apply = apply, + stop = stop, +} diff --git a/example_cluster/tarantool3_project/router.lua b/example_cluster/tarantool3_project/router.lua new file mode 100644 index 00000000..89f94a82 --- /dev/null +++ b/example_cluster/tarantool3_project/router.lua @@ -0,0 +1,22 @@ +local clock = require('clock') +local fiber = require('fiber') +local log = require('log') + +local vshard = require('vshard') + +local TIMEOUT = 60 +local DELAY = 0.1 + +local start = clock.monotonic() +while clock.monotonic() - start < TIMEOUT do + local ok, err = vshard.router.bootstrap({ + if_not_bootstrapped = true, + }) + + if ok then + break + end + + log.info(('Router bootstrap error: %s'):format(err)) + fiber.sleep(DELAY) +end diff --git a/example_cluster/tarantool3_project/tarantool3_project-scm-1.rockspec b/example_cluster/tarantool3_project/tarantool3_project-scm-1.rockspec new file mode 100644 index 00000000..8a69519f --- /dev/null +++ b/example_cluster/tarantool3_project/tarantool3_project-scm-1.rockspec @@ -0,0 +1,16 @@ +package = 'tarantool3_project' +version = 'scm-1' +source = { + url = '/dev/null', +} +dependencies = { + 'tarantool', + 'lua >= 5.1', + 'crud == 1.5.2-1', + 'metrics == scm-1', + 'expirationd == 1.3.1-1', + 'http == 1.5.0-1', +} +build = { + type = 'none'; +} diff --git a/supported_metrics.md b/supported_metrics.md index cb0cb2b2..a0ae1572 100644 --- a/supported_metrics.md +++ b/supported_metrics.md @@ -5,7 +5,8 @@ Format is as follows. # tarantool/metrics -Based on [tarantool/metrics 0.16.0](https://github.com/tarantool/metrics/releases/tag/0.16.0). +TODO: put here a proper link after release +Based on [tarantool/metrics master](https://github.com/tarantool/metrics/pull/491). - [x] **tnt_clock_delta**: see *Replication overview/Instances clock delta* panel ([#133](https://github.com/tarantool/grafana-dashboard/issues/133)) - [x] **tnt_cpu_user_time**: see *Tarantool CPU statistics/CPU user time* panel ([#71](https://github.com/tarantool/grafana-dashboard/issues/71)) @@ -150,11 +151,19 @@ Based on [tarantool/metrics 0.16.0](https://github.com/tarantool/metrics/release - [x] **tnt_memtx_mvcc_tuples_read_view_retained**: see *Tarantool MVCC overview/Retained tuples in read views*, *Tarantool MVCC overview/Retained tuples in read views size* panels ([#197](https://github.com/tarantool/grafana-dashboard/issues/197)) - [x] **tnt_memtx_mvcc_tuples_tracking_stories**: see *Tarantool MVCC overview/Stories tuples tracked*, *Tarantool MVCC overview/Stories tuples tracked size* panels ([#197](https://github.com/tarantool/grafana-dashboard/issues/197)) - [x] **tnt_memtx_mvcc_tuples_tracking_retained**: see *Tarantool MVCC overview/Retained tuples tracked*, *Tarantool MVCC overview/Retained tuples tracked size* panels ([#197](https://github.com/tarantool/grafana-dashboard/issues/197)) +- [ ] **tnt_memtx_tuples_data_total** ([#226])(https://github.com/tarantool/grafana-dashboard/issues/226)) +- [ ] **tnt_memtx_tuples_data_read_view** ([#226])(https://github.com/tarantool/grafana-dashboard/issues/226)) +- [ ] **tnt_memtx_tuples_data_garbage** ([#226])(https://github.com/tarantool/grafana-dashboard/issues/226)) +- [ ] **tnt_memtx_index_total** ([#226])(https://github.com/tarantool/grafana-dashboard/issues/226)) +- [ ] **tnt_memtx_index_read_view** ([#226])(https://github.com/tarantool/grafana-dashboard/issues/226)) +- [ ] **tnt_vinyl_memory_tuple** ([#226])(https://github.com/tarantool/grafana-dashboard/issues/226)) +- [ ] **tnt_config_alerts** ([#224])(https://github.com/tarantool/grafana-dashboard/issues/224)) +- [ ] **tnt_config_status** ([#224])(https://github.com/tarantool/grafana-dashboard/issues/224)) - [x] **http_server_request_latency**, **http_server_request_latency_sum**, **http_server_request_latency_count**: see *Tarantool HTTP statistics/Success requests (code 2xx)*, *Tarantool HTTP statistics/Error requests (code 4xx)* *Tarantool HTTP statistics/Error requests (code 5xx)* *Tarantool HTTP statistics/Success requests latency (code 2xx)* *Tarantool HTTP statistics/Error requests latency (code 4xx)* *Tarantool HTTP statistics/Error requests latency (code 5xx)* ([dbb3374f](https://github.com/tarantool/grafana-dashboard/commit/dbb3374f214aaa069e5574960afd65f44f5ae0cd)) # tarantool/crud -Based on [tarantool/crud 1.0.0](https://github.com/tarantool/crud/releases/tag/1.0.0). +Based on [tarantool/crud 1.5.2](https://github.com/tarantool/crud/releases/tag/1.5.2). - [x] **tnt_crud_stats**, **tnt_crud_stats_sum**, **tnt_crud_stats_count**: see *CRUD module statistics/SELECT success requests*, *CRUD module statistics/SELECT success requests latency*, *CRUD module statistics/SELECT error requests*, *CRUD module statistics/SELECT error requests latency*, *CRUD module statistics/SELECT tuples fetched*, *CRUD module statistics/SELECT tuples lookup*, *CRUD module statistics/INSERT success requests*, *CRUD module statistics/INSERT success requests latency*, *CRUD module statistics/INSERT error requests*, *CRUD module statistics/INSERT error requests latency*, *CRUD module statistics/INSERT MANY success requests*, *CRUD module statistics/INSERT MANY success requests latency*, *CRUD module statistics/INSERT MANY error requests*, *CRUD module statistics/INSERT MANY error requests latency*, *CRUD module statistics/REPLACE success requests*, *CRUD module statistics/REPLACE success requests latency*, *CRUD module statistics/REPLACE error requests*, *CRUD module statistics/REPLACE error requests latency*, *CRUD module statistics/REPLACE MANY success requests*, *CRUD module statistics/REPLACE MANY success requests latency*, *CRUD module statistics/REPLACE MANY error requests*, *CRUD module statistics/REPLACE MANY error requests latency*, *CRUD module statistics/UPSERT success requests*, *CRUD module statistics/UPSERT success requests latency*, *CRUD module statistics/UPSERT error requests*, *CRUD module statistics/UPSERT error requests latency*, *CRUD module statistics/UPSERT MANY success requests*, *CRUD module statistics/UPSERT MANY success requests latency*, *CRUD module statistics/UPSERT MANY error requests*, *CRUD module statistics/UPSERT MANY error requests latency*, *CRUD module statistics/UPDATE success requests*, *CRUD module statistics/UPDATE success requests latency*, *CRUD module statistics/UPDATE error requests*, *CRUD module statistics/UPDATE error requests latency*, *CRUD module statistics/DELETE success requests*, *CRUD module statistics/DELETE success requests latency*, *CRUD module statistics/DELETE error requests*, *CRUD module statistics/DELETE error requests latency*, *CRUD module statistics/COUNT success requests*, *CRUD module statistics/COUNT success requests latency*, *CRUD module statistics/COUNT error requests*, *CRUD module statistics/COUNT error requests latency*, *CRUD module statistics/GET success requests*, *CRUD module statistics/GET success requests latency*, *CRUD module statistics/GET error requests*, *CRUD module statistics/GET error requests latency*, *CRUD module statistics/BORDERS success requests*, *CRUD module statistics/BORDERS success requests latency*, *CRUD module statistics/BORDERS error requests*, *CRUD module statistics/BORDERS error requests latency*, *CRUD module statistics/LEN success requests*, *CRUD module statistics/LEN success requests latency*, *CRUD module statistics/LEN error requests*, *CRUD module statistics/LEN error requests latency*, *CRUD module statistics/TRUNCATE success requests*, *CRUD module statistics/TRUNCATE success requests latency*, *CRUD module statistics/TRUNCATE error requests*, *CRUD module statistics/TRUNCATE error requests latency* panels ([#143](https://github.com/tarantool/grafana-dashboard/pull/143)) - [x] **tnt_crud_map_reduces**: see *CRUD module statistics/Map reduce SELECT requests* panel ([#143](https://github.com/tarantool/grafana-dashboard/pull/143)) @@ -163,7 +172,7 @@ Based on [tarantool/crud 1.0.0](https://github.com/tarantool/crud/releases/tag/1 # tarantool/expirationd -Based on [tarantool/expirationd 1.3.1](https://github.com/tarantool/expirationd/releases/tag/1.3.1). +Based on [tarantool/expirationd 1.6.0](https://github.com/tarantool/expirationd/releases/tag/1.6.0). - [x] **expirationd_checked_count**: see *expirationd module statistics/Tuples checked* panel ([#162](https://github.com/tarantool/grafana-dashboard/pull/162)) - [x] **expirationd_expired_count**: see *expirationd module statistics/Tuples expired* panel ([#162](https://github.com/tarantool/grafana-dashboard/pull/162))