diff --git a/.env.example b/.env.example index 84eebbe452..a5fc45bc6a 100644 --- a/.env.example +++ b/.env.example @@ -219,6 +219,10 @@ # of reports resubmittd on each run. # USAGE_TRACKING_RESUBMISSION_BATCH_SIZE=10 +# Generating usage tracking reports can result in increased memory usage. +# Decreasing the run batch size shouls reduce the memory consumption. +# USAGE_TRACKING_RUN_CHUNK_SIZE=100 + # OpenFn.org hosts a public sandbox that gets reset every night. If you'd like to # make your instance "resettable" (a highly destructive action—this destroys all # data in your instance) you can set the following environment variable to "yes" diff --git a/CHANGELOG.md b/CHANGELOG.md index 8467e44c16..0b468d672e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,8 @@ and this project adheres to - Error when the logger receives a boolean [#2666](https://github.com/OpenFn/lightning/issues/2666) +- Attempt to reduce memory consumption when generating UsageTracking reports. + [#2636](https://github.com/OpenFn/lightning/issues/2636) ## [v2.10.1] - 2024-11-13 diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index a65fa5b85b..d370341394 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -152,6 +152,7 @@ For Google Cloud Storage, the following environment variables are required: | `USAGE_TRACKING_DAILY_BATCH_SIZE` | The number of days that will be reported on with each run of `UsageTracking.DayWorker`. This will only have a noticeable effect in cases where there is a backlog or where reports are being generated retroactively (defaults to 10). | | `USAGE_TRACKING_ENABLED` | Enables the submission of anonymized usage data to OpenFn (defaults to `true`) | | `USAGE_TRACKING_RESUBMISSION_BATCH_SIZE` | The number of failed reports that will be submitted on each resubmission run (defaults to 10) | +| `USAGE_TRACKING_RUN_CHUNK_SIZE` | The size of each batch of runs that is streamed from the database when generating UsageTracking reports (default 100). Decreasing this may decrease memory consumption when generating reports. | | `USAGE_TRACKING_UUIDS` | Indicates whether submissions should include cleartext UUIDs or not. Options are `cleartext` or `hashed_only`, with the default being `hashed_only`. | ### AI Chat diff --git a/lib/lightning/config.ex b/lib/lightning/config.ex index ca675ebaf8..8084b63806 100644 --- a/lib/lightning/config.ex +++ b/lib/lightning/config.ex @@ -120,11 +120,6 @@ defmodule Lightning.Config do |> Keyword.get(:sender_name) end - @impl true - def usage_tracking do - Application.get_env(:lightning, :usage_tracking) - end - @impl true def reset_password_token_validity_in_days do 1 @@ -141,6 +136,31 @@ defmodule Lightning.Config do |> Keyword.get(key) end + @impl true + def usage_tracking do + Application.get_env(:lightning, :usage_tracking) + end + + @impl true + def usage_tracking_cleartext_uuids_enabled? do + usage_tracking() |> Keyword.get(:cleartext_uuids_enabled) + end + + @impl true + def usage_tracking_enabled? do + usage_tracking() |> Keyword.get(:enabled) + end + + @impl true + def usage_tracking_host do + usage_tracking() |> Keyword.get(:host) + end + + @impl true + def usage_tracking_run_chunk_size do + usage_tracking() |> Keyword.get(:run_chunk_size) + end + @impl true def usage_tracking_cron_opts do opts = usage_tracking() @@ -235,7 +255,11 @@ defmodule Lightning.Config do @callback storage(key :: atom()) :: term() @callback token_signer() :: Joken.Signer.t() @callback usage_tracking() :: Keyword.t() + @callback usage_tracking_cleartext_uuids_enabled?() :: boolean() @callback usage_tracking_cron_opts() :: [Oban.Plugins.Cron.cron_input()] + @callback usage_tracking_enabled?() :: boolean() + @callback usage_tracking_host() :: String.t() + @callback usage_tracking_run_chunk_size() :: integer() @callback worker_secret() :: binary() | nil @callback worker_token_signer() :: Joken.Signer.t() @@ -321,10 +345,6 @@ defmodule Lightning.Config do impl().email_sender_name() end - def usage_tracking do - impl().usage_tracking() - end - def reset_password_token_validity_in_days do impl().reset_password_token_validity_in_days() end @@ -337,10 +357,30 @@ defmodule Lightning.Config do impl().storage(key) end + def usage_tracking do + impl().usage_tracking() + end + + def usage_tracking_cleartext_uuids_enabled? do + impl().usage_tracking_cleartext_uuids_enabled?() + end + def usage_tracking_cron_opts do impl().usage_tracking_cron_opts() end + def usage_tracking_enabled? do + impl().usage_tracking_enabled?() + end + + def usage_tracking_host do + impl().usage_tracking_host() + end + + def usage_tracking_run_chunk_size do + impl().usage_tracking_run_chunk_size() + end + def kafka_triggers_enabled? do impl().kafka_triggers_enabled?() end diff --git a/lib/lightning/config/bootstrap.ex b/lib/lightning/config/bootstrap.ex index 9171f71662..32ccea0b65 100644 --- a/lib/lightning/config/bootstrap.ex +++ b/lib/lightning/config/bootstrap.ex @@ -484,7 +484,8 @@ defmodule Lightning.Config.Bootstrap do host: env!("USAGE_TRACKER_HOST", :string, "https://impact.openfn.org"), resubmission_batch_size: env!("USAGE_TRACKING_RESUBMISSION_BATCH_SIZE", :integer, 10), - daily_batch_size: env!("USAGE_TRACKING_DAILY_BATCH_SIZE", :integer, 10) + daily_batch_size: env!("USAGE_TRACKING_DAILY_BATCH_SIZE", :integer, 10), + run_chunk_size: env!("USAGE_TRACKING_RUN_CHUNK_SIZE", :integer, 100) config :lightning, :kafka_triggers, alternate_storage_enabled: diff --git a/lib/lightning/usage_tracking/project_metrics_service.ex b/lib/lightning/usage_tracking/project_metrics_service.ex index 85f37185a0..e1f6ff4eea 100644 --- a/lib/lightning/usage_tracking/project_metrics_service.ex +++ b/lib/lightning/usage_tracking/project_metrics_service.ex @@ -17,7 +17,7 @@ defmodule Lightning.UsageTracking.ProjectMetricsService do query = from p in Project, where: p.inserted_at <= ^report_time, - preload: [:users, [workflows: [:jobs, runs: [steps: [:job]]]]] + preload: [:users, [workflows: [:jobs]]] query |> Repo.all() end diff --git a/lib/lightning/usage_tracking/report_worker.ex b/lib/lightning/usage_tracking/report_worker.ex index 5837780a8f..844cf14057 100644 --- a/lib/lightning/usage_tracking/report_worker.ex +++ b/lib/lightning/usage_tracking/report_worker.ex @@ -14,16 +14,18 @@ defmodule Lightning.UsageTracking.ReportWorker do def perform(%{args: %{"date" => date_string}}) do date = Date.from_iso8601!(date_string) - env = Application.get_env(:lightning, :usage_tracking) - config = UsageTracking.find_enabled_daily_report_config() - if env[:enabled] && config do - cleartext_uuids_enabled = env[:cleartext_uuids_enabled] + if Lightning.Config.usage_tracking_enabled?() && config do + cleartext_uuids_enabled = + Lightning.Config.usage_tracking_cleartext_uuids_enabled?() case UsageTracking.insert_report(config, cleartext_uuids_enabled, date) do {:ok, report} -> - report |> UsageTracking.submit_report(env[:host]) + UsageTracking.submit_report( + report, + Lightning.Config.usage_tracking_host() + ) _error -> nil diff --git a/lib/lightning/usage_tracking/run_service.ex b/lib/lightning/usage_tracking/run_service.ex index 3cac47fb15..2306c0f0c3 100644 --- a/lib/lightning/usage_tracking/run_service.ex +++ b/lib/lightning/usage_tracking/run_service.ex @@ -4,14 +4,8 @@ defmodule Lightning.UsageTracking.RunService do """ - def finished_runs(all_runs, date) do - all_runs - |> finished_on(date) - end - - def finished_steps(runs, date) do - runs - |> Enum.flat_map(& &1.steps) + def finished_steps(run, date) do + run.steps |> finished_on(date) end diff --git a/lib/lightning/usage_tracking/workflow_metrics_query.ex b/lib/lightning/usage_tracking/workflow_metrics_query.ex new file mode 100644 index 0000000000..8e39d106da --- /dev/null +++ b/lib/lightning/usage_tracking/workflow_metrics_query.ex @@ -0,0 +1,31 @@ +defmodule Lightning.UsageTracking.WorkflowMetricsQuery do + @moduledoc """ + Query module to support workflow metrics while allowing for fewer test + permutations. + + + """ + import Ecto.Query + + alias Lightning.Run + + def workflow_runs(%{id: workflow_id}) do + from r in Run, + join: w in assoc(r, :workflow), + on: w.id == ^workflow_id + end + + def runs_finished_on(query, date) do + start_of_day = DateTime.new!(date, ~T[00:00:00]) + + start_of_next_day = + date + |> Date.add(1) + |> DateTime.new!(~T[00:00:00]) + + from r in query, + where: not is_nil(r.finished_at), + where: r.finished_at >= ^start_of_day, + where: r.finished_at < ^start_of_next_day + end +end diff --git a/lib/lightning/usage_tracking/workflow_metrics_service.ex b/lib/lightning/usage_tracking/workflow_metrics_service.ex index 7451a0881c..2200e5cd11 100644 --- a/lib/lightning/usage_tracking/workflow_metrics_service.ex +++ b/lib/lightning/usage_tracking/workflow_metrics_service.ex @@ -4,7 +4,10 @@ defmodule Lightning.UsageTracking.WorkflowMetricsService do """ + + alias Lightning.Repo alias Lightning.UsageTracking.RunService + alias Lightning.UsageTracking.WorkflowMetricsQuery def find_eligible_workflows(workflows, date) do workflows @@ -12,17 +15,68 @@ defmodule Lightning.UsageTracking.WorkflowMetricsService do end def generate_metrics(workflow, cleartext_enabled, date) do - runs = RunService.finished_runs(workflow.runs, date) - steps = RunService.finished_steps(workflow.runs, date) - active_jobs = RunService.unique_job_ids(steps, date) + workflow + |> metrics_for_runs(date) + |> add_active_jobs() + |> Map.delete(:job_ids) + |> Map.merge(instrument_identity(workflow.id, cleartext_enabled)) + end + + defp metrics_for_runs(workflow, date) do + chunk_size = Lightning.Config.usage_tracking_run_chunk_size() + + {:ok, updated_metrics} = + Repo.transaction(fn -> + workflow + |> WorkflowMetricsQuery.workflow_runs() + |> WorkflowMetricsQuery.runs_finished_on(date) + |> Repo.stream(max_rows: chunk_size) + |> Stream.chunk_every(chunk_size) + |> Enum.flat_map(&preload_assocs/1) + |> Enum.reduce(base_metrics(workflow), metric_updater(date)) + end) + + updated_metrics + end + + defp preload_assocs(run_chunk), do: Repo.preload(run_chunk, steps: [:job]) + defp base_metrics(%{jobs: jobs}) do %{ - no_of_active_jobs: Enum.count(active_jobs), - no_of_jobs: Enum.count(workflow.jobs), - no_of_runs: Enum.count(runs), - no_of_steps: Enum.count(steps) + no_of_jobs: Enum.count(jobs), + no_of_runs: 0, + no_of_steps: 0, + job_ids: [] } - |> Map.merge(instrument_identity(workflow.id, cleartext_enabled)) + end + + defp metric_updater(date) do + fn run, acc -> + %{ + no_of_steps: no_of_steps, + no_of_runs: no_of_runs, + job_ids: job_ids + } = acc + + steps = RunService.finished_steps(run, date) + active_jobs = RunService.unique_job_ids(steps, date) + + Map.merge( + acc, + %{ + no_of_runs: no_of_runs + 1, + no_of_steps: no_of_steps + Enum.count(steps), + job_ids: Enum.concat(job_ids, active_jobs) + } + ) + end + end + + defp add_active_jobs(%{job_ids: job_ids} = metrics) do + metrics + |> Map.merge(%{ + no_of_active_jobs: job_ids |> Enum.uniq() |> Enum.count() + }) end defp instrument_identity(identity, false = _cleartext_enabled) do diff --git a/test/lightning/usage_tracking/report_worker_test.exs b/test/lightning/usage_tracking/report_worker_test.exs index 261fadc251..fdd47cc277 100644 --- a/test/lightning/usage_tracking/report_worker_test.exs +++ b/test/lightning/usage_tracking/report_worker_test.exs @@ -2,8 +2,8 @@ defmodule Lightning.UsageTracking.ReportWorkerTest do use Lightning.DataCase, async: true import Mock + import Mox import Tesla.Mock - import Lightning.ApplicationHelpers, only: [put_temporary_env: 3] alias Lightning.UsageTracking alias Lightning.UsageTracking.GithubClient @@ -23,11 +23,12 @@ defmodule Lightning.UsageTracking.ReportWorkerTest do report_config = UsageTracking.enable_daily_report(DateTime.utc_now()) - put_temporary_env(:lightning, :usage_tracking, - cleartext_uuids_enabled: cleartext_uuids_enabled, - enabled: true, - host: @host - ) + stub(Lightning.MockConfig, :usage_tracking_cleartext_uuids_enabled?, fn -> + cleartext_uuids_enabled + end) + + stub(Lightning.MockConfig, :usage_tracking_enabled?, fn -> true end) + stub(Lightning.MockConfig, :usage_tracking_host, fn -> @host end) %{instance: instance_metrics} = ReportData.generate(report_config, cleartext_uuids_enabled, @date) @@ -88,11 +89,12 @@ defmodule Lightning.UsageTracking.ReportWorkerTest do report_config = UsageTracking.enable_daily_report(DateTime.utc_now()) - put_temporary_env(:lightning, :usage_tracking, - cleartext_uuids_enabled: cleartext_uuids_enabled, - enabled: true, - host: @host - ) + stub(Lightning.MockConfig, :usage_tracking_cleartext_uuids_enabled?, fn -> + cleartext_uuids_enabled + end) + + stub(Lightning.MockConfig, :usage_tracking_enabled?, fn -> true end) + stub(Lightning.MockConfig, :usage_tracking_host, fn -> @host end) %{instance: instance_metrics} = ReportData.generate(report_config, cleartext_uuids_enabled, @date) @@ -150,11 +152,12 @@ defmodule Lightning.UsageTracking.ReportWorkerTest do ]) do UsageTracking.enable_daily_report(DateTime.utc_now()) - put_temporary_env(:lightning, :usage_tracking, - cleartext_uuids_enabled: false, - enabled: true, - host: @host - ) + stub(Lightning.MockConfig, :usage_tracking_cleartext_uuids_enabled?, fn -> + false + end) + + stub(Lightning.MockConfig, :usage_tracking_enabled?, fn -> true end) + stub(Lightning.MockConfig, :usage_tracking_host, fn -> @host end) insert(:usage_tracking_report, report_date: @date, data: %{}) @@ -178,11 +181,14 @@ defmodule Lightning.UsageTracking.ReportWorkerTest do setup do UsageTracking.disable_daily_report() - put_temporary_env(:lightning, :usage_tracking, - cleartext_uuids_enabled: false, - enabled: true, - host: @host - ) + stub(Lightning.MockConfig, :usage_tracking_cleartext_uuids_enabled?, fn -> + false + end) + + stub(Lightning.MockConfig, :usage_tracking_enabled?, fn -> true end) + stub(Lightning.MockConfig, :usage_tracking_host, fn -> @host end) + + :ok end test "does not submit metrics to the ImpactTracker" do @@ -210,10 +216,14 @@ defmodule Lightning.UsageTracking.ReportWorkerTest do setup do UsageTracking.enable_daily_report(DateTime.utc_now()) - put_temporary_env(:lightning, :usage_tracking, - enabled: false, - host: @host - ) + stub(Lightning.MockConfig, :usage_tracking_cleartext_uuids_enabled?, fn -> + false + end) + + stub(Lightning.MockConfig, :usage_tracking_enabled?, fn -> false end) + stub(Lightning.MockConfig, :usage_tracking_host, fn -> @host end) + + :ok end test "does not submit metrics to the ImpactTracker" do diff --git a/test/lightning/usage_tracking/run_service_test.exs b/test/lightning/usage_tracking/run_service_test.exs index d7e374afdc..5fe2d27da7 100644 --- a/test/lightning/usage_tracking/run_service_test.exs +++ b/test/lightning/usage_tracking/run_service_test.exs @@ -10,58 +10,9 @@ defmodule Lightning.UsageTracking.RunServiceTest do @finished_at ~U[2024-02-05 12:11:10Z] @other_finished_at ~U[2024-02-04 12:11:10Z] - describe ".finished_runs/2" do - test "returns the subset of runs finished on the given date" do - finished_on_report_date = insert_finished_runs(@finished_at) - finished_on_other_date = insert_finished_runs(@other_finished_at) - unfinished = insert_unfinished_runs() - - run_list = finished_on_other_date ++ finished_on_report_date ++ unfinished - - assert( - RunService.finished_runs(run_list, @date) == finished_on_report_date - ) - end - - defp insert_finished_runs(finished_at) do - Run.final_states() - |> Enum.map(fn state -> - insert( - :run, - state: state, - finished_at: finished_at, - work_order: build(:workorder), - dataclip: build(:dataclip), - starting_job: build(:job) - ) - end) - end - - defp insert_unfinished_runs do - [:available, :claimed, :started] - |> Enum.map(fn state -> - insert( - :run, - state: state, - work_order: build(:workorder), - dataclip: build(:dataclip), - starting_job: build(:job) - ) - end) - end - end - describe ".finished_steps/2" do test "returns all run steps that finished on report date" do - run_1 = - insert( - :run, - work_order: build(:workorder), - dataclip: build(:dataclip), - starting_job: build(:job) - ) - - run_2 = + run = insert( :run, work_order: build(:workorder), @@ -69,16 +20,12 @@ defmodule Lightning.UsageTracking.RunServiceTest do starting_job: build(:job) ) - finished_1 = insert_steps(run_1) - finished_2 = insert_steps(run_2) - - run_1 = run_1 |> Repo.preload(:steps) - run_2 = run_2 |> Repo.preload(:steps) + finished = insert_steps(run) - runs = [run_1, run_2] + run = run |> Repo.preload(:steps) - expected_ids = (finished_1 ++ finished_2) |> MapSet.new(& &1.id) - actual_ids = RunService.finished_steps(runs, @date) |> MapSet.new(& &1.id) + expected_ids = MapSet.new(finished, & &1.id) + actual_ids = RunService.finished_steps(run, @date) |> MapSet.new(& &1.id) assert(actual_ids == expected_ids) end diff --git a/test/lightning/usage_tracking/workflow_metrics_query_test.exs b/test/lightning/usage_tracking/workflow_metrics_query_test.exs new file mode 100644 index 0000000000..4756392121 --- /dev/null +++ b/test/lightning/usage_tracking/workflow_metrics_query_test.exs @@ -0,0 +1,142 @@ +defmodule Lightning.UsageTracking.WorkflowMetricsQueryTest do + use Lightning.DataCase, async: true + + alias Lightning.Repo + alias Lightning.Run + alias Lightning.UsageTracking.WorkflowMetricsQuery + + describe "workflow_runs" do + setup do + workflow = insert(:workflow) + other_workflow = insert(:workflow) + + work_order_1 = insert(:workorder, workflow: workflow) + work_order_2 = insert(:workorder, workflow: workflow) + + other_work_order = insert(:workorder, workflow: other_workflow) + + run_1 = + insert( + :run, + work_order: work_order_1, + dataclip: build(:dataclip), + starting_job: build(:job) + ) + + run_2 = + insert( + :run, + work_order: work_order_2, + dataclip: build(:dataclip), + starting_job: build(:job) + ) + + _other_run = + insert( + :run, + work_order: other_work_order, + dataclip: build(:dataclip), + starting_job: build(:job) + ) + + %{ + run_1: run_1, + run_2: run_2, + workflow: workflow + } + end + + test "returns all runs linked to the given workflow", %{ + run_1: %{id: run_1_id}, + run_2: %{id: run_2_id}, + workflow: workflow + } do + runs = WorkflowMetricsQuery.workflow_runs(workflow) |> Repo.all() + + expected_ids = [run_1_id, run_2_id] |> Enum.sort() + + actual_ids = runs |> Enum.map(fn %Run{id: id} -> id end) |> Enum.sort() + + assert actual_ids == expected_ids + end + end + + describe "runs_finished_on" do + setup do + work_order = insert(:workorder, workflow: build(:workflow)) + + date = ~D[2024-11-13] + inclusive_start_time = DateTime.new!(date, ~T[00:00:00]) + exclusive_end_time = DateTime.new!(Date.add(date, 1), ~T[00:00:00]) + + _null_finished_at_run = + insert( + :run, + work_order: work_order, + dataclip: build(:dataclip), + starting_job: build(:job) + ) + + _too_early_run = + insert( + :run, + work_order: work_order, + dataclip: build(:dataclip), + starting_job: build(:job), + finished_at: DateTime.add(inclusive_start_time, -1, :second) + ) + + run_1 = + insert( + :run, + work_order: work_order, + dataclip: build(:dataclip), + starting_job: build(:job), + finished_at: inclusive_start_time + ) + + run_2 = + insert( + :run, + work_order: work_order, + dataclip: build(:dataclip), + starting_job: build(:job), + finished_at: DateTime.add(exclusive_end_time, -1, :second) + ) + + _too_late_run = + insert( + :run, + work_order: work_order, + dataclip: build(:dataclip), + starting_job: build(:job), + finished_at: exclusive_end_time + ) + + %{ + date: date, + run_1: run_1, + run_2: run_2 + } + end + + test "returns all runs that finished on the given date", %{ + date: date, + run_1: %{id: run_1_id}, + run_2: %{id: run_2_id} + } do + base_query = from(r in Run) + + runs = + base_query + |> WorkflowMetricsQuery.runs_finished_on(date) + |> Repo.all() + + expected_ids = [run_1_id, run_2_id] |> Enum.sort() + + actual_ids = runs |> Enum.map(fn %Run{id: id} -> id end) |> Enum.sort() + + assert actual_ids == expected_ids + end + end +end diff --git a/test/lightning/usage_tracking/workflow_metrics_service_test.exs b/test/lightning/usage_tracking/workflow_metrics_service_test.exs index c6944555aa..007ab1f46d 100644 --- a/test/lightning/usage_tracking/workflow_metrics_service_test.exs +++ b/test/lightning/usage_tracking/workflow_metrics_service_test.exs @@ -8,214 +8,81 @@ defmodule Lightning.UsageTracking.WorkflowMetricsServiceTest do @hashed_id "EECF8CFDD120E8DF8D9A12CA92AC3E815908223F95CFB11F19261A3C0EB34AEC" @workflow_id "3cfb674b-e878-470d-b7c0-cfa8f7e003ae" - setup do - no_of_jobs = 6 - no_of_work_orders = 3 - no_of_runs_per_work_order = 2 - no_of_unique_jobs_in_steps = 2 - no_of_steps_per_unique_job = 4 - - workflow = - build_workflow( - @workflow_id, - no_of_jobs: no_of_jobs, - no_of_work_orders: no_of_work_orders, - no_of_runs_per_work_order: no_of_runs_per_work_order, - no_of_unique_jobs_in_steps: no_of_unique_jobs_in_steps, - no_of_steps_per_unique_job: no_of_steps_per_unique_job - ) - - _other_workflow = - build_workflow( - Ecto.UUID.generate(), - no_of_jobs: no_of_jobs + 1, - no_of_work_orders: no_of_work_orders + 1, - no_of_runs_per_work_order: no_of_runs_per_work_order + 1, - no_of_unique_jobs_in_steps: no_of_unique_jobs_in_steps + 1, - no_of_steps_per_unique_job: no_of_steps_per_unique_job + 1 - ) - - no_of_runs = no_of_work_orders * no_of_runs_per_work_order - - no_of_steps = - no_of_runs * no_of_unique_jobs_in_steps * no_of_steps_per_unique_job - - %{ - workflow: workflow, - no_of_jobs: no_of_jobs, - no_of_runs: no_of_runs, - no_of_steps: no_of_steps, - no_of_unique_jobs: no_of_unique_jobs_in_steps - } - end - - describe "generate_metrics/3 - cleartext disabled" do - setup context do - context |> Map.merge(%{enabled: false}) - end - - test "includes the hashed workflow uuid", config do - %{workflow: workflow, enabled: enabled} = config - - hashed_uuid = @hashed_id - - assert( - %{ - hashed_uuid: ^hashed_uuid - } = WorkflowMetricsService.generate_metrics(workflow, enabled, @date) - ) - end - - test "does not include the cleartext uuid", config do - %{workflow: workflow, enabled: enabled} = config - - assert( - %{ - cleartext_uuid: nil - } = WorkflowMetricsService.generate_metrics(workflow, enabled, @date) - ) - end - - test "includes the number of jobs", config do - %{ - workflow: workflow, - enabled: enabled, - no_of_jobs: no_of_jobs - } = config - - assert( - %{ - no_of_jobs: ^no_of_jobs - } = WorkflowMetricsService.generate_metrics(workflow, enabled, @date) - ) - end - - test "includes the number of finished runs", config do - %{ - workflow: workflow, - enabled: enabled, - no_of_runs: no_of_runs - } = config - - assert( - %{ - no_of_runs: ^no_of_runs - } = WorkflowMetricsService.generate_metrics(workflow, enabled, @date) - ) - end - - test "includes the number of steps for the finished runs", config do - %{ - workflow: workflow, - enabled: enabled, - no_of_steps: no_of_steps - } = config - - assert( - %{ - no_of_steps: ^no_of_steps - } = WorkflowMetricsService.generate_metrics(workflow, enabled, @date) - ) - end - - test "includes the number of active jobs for the finished steps", config do - %{ - workflow: workflow, - enabled: enabled, - no_of_unique_jobs: no_of_unique_jobs - } = config - - assert( - %{ - no_of_active_jobs: ^no_of_unique_jobs - } = WorkflowMetricsService.generate_metrics(workflow, enabled, @date) - ) - end - end - - describe "generate_metrics/3 - cleartext enabled" do - setup context do - context |> Map.merge(%{enabled: true}) - end - - test "includes the hashed workflow uuid", config do - %{workflow: workflow, enabled: enabled} = config - - hashed_uuid = @hashed_id - - assert( - %{ - hashed_uuid: ^hashed_uuid - } = WorkflowMetricsService.generate_metrics(workflow, enabled, @date) - ) - end - - test "includes the cleartext uuid", config do - %{workflow: workflow, enabled: enabled} = config - - cleartext_uuid = @workflow_id - - assert( - %{ - cleartext_uuid: ^cleartext_uuid - } = WorkflowMetricsService.generate_metrics(workflow, enabled, @date) - ) - end + describe "generate_metrics/3" do + setup do + no_of_jobs = 6 + no_of_work_orders = 3 + no_of_runs_per_work_order = 2 + no_of_unique_jobs_in_steps = 2 + no_of_steps_per_unique_job = 4 + + workflow = + build_workflow( + @workflow_id, + no_of_jobs: no_of_jobs, + no_of_work_orders: no_of_work_orders, + no_of_runs_per_work_order: no_of_runs_per_work_order, + no_of_unique_jobs_in_steps: no_of_unique_jobs_in_steps, + no_of_steps_per_unique_job: no_of_steps_per_unique_job + ) - test "includes the number of jobs", config do - %{ - workflow: workflow, - enabled: enabled, - no_of_jobs: no_of_jobs - } = config - - assert( - %{ - no_of_jobs: ^no_of_jobs - } = WorkflowMetricsService.generate_metrics(workflow, enabled, @date) - ) - end + _other_workflow = + build_workflow( + Ecto.UUID.generate(), + no_of_jobs: no_of_jobs + 1, + no_of_work_orders: no_of_work_orders + 1, + no_of_runs_per_work_order: no_of_runs_per_work_order + 1, + no_of_unique_jobs_in_steps: no_of_unique_jobs_in_steps + 1, + no_of_steps_per_unique_job: no_of_steps_per_unique_job + 1 + ) - test "includes the number of finished runs", config do - %{ - workflow: workflow, - enabled: enabled, - no_of_runs: no_of_runs - } = config - - assert( - %{ - no_of_runs: ^no_of_runs - } = WorkflowMetricsService.generate_metrics(workflow, enabled, @date) - ) - end + no_of_runs = no_of_work_orders * no_of_runs_per_work_order - test "includes the number of finished steps", config do - %{ - workflow: workflow, - enabled: enabled, - no_of_steps: no_of_steps - } = config - - assert( - %{ - no_of_steps: ^no_of_steps - } = WorkflowMetricsService.generate_metrics(workflow, enabled, @date) - ) - end + no_of_steps = + no_of_runs * no_of_unique_jobs_in_steps * no_of_steps_per_unique_job - test "includes the number of active jobs for the finished steps", config do %{ workflow: workflow, - enabled: enabled, - no_of_unique_jobs: no_of_unique_jobs - } = config - - assert( - %{ - no_of_active_jobs: ^no_of_unique_jobs - } = WorkflowMetricsService.generate_metrics(workflow, enabled, @date) - ) + no_of_jobs: no_of_jobs, + no_of_runs: no_of_runs, + no_of_steps: no_of_steps, + no_of_unique_jobs: no_of_unique_jobs_in_steps + } + end + + test "returns the appropriate metrics depending on cleartext_uuids are enable or not", + %{ + no_of_jobs: no_of_jobs, + no_of_runs: no_of_runs, + no_of_steps: no_of_steps, + no_of_unique_jobs: no_of_unique_jobs, + workflow: workflow + } do + Mox.expect(Lightning.MockConfig, :usage_tracking_run_chunk_size, 2, fn -> + 100 + end) + + assert %{ + hashed_uuid: @hashed_id, + cleartext_uuid: nil, + no_of_active_jobs: no_of_unique_jobs, + no_of_jobs: no_of_jobs, + no_of_runs: no_of_runs, + no_of_steps: no_of_steps + } == + WorkflowMetricsService.generate_metrics(workflow, false, @date) + + assert %{ + hashed_uuid: @hashed_id, + cleartext_uuid: @workflow_id, + no_of_active_jobs: no_of_unique_jobs, + no_of_jobs: no_of_jobs, + no_of_runs: no_of_runs, + no_of_steps: no_of_steps + } == + WorkflowMetricsService.generate_metrics(workflow, true, @date) + + Mox.verify!() end end