defmodule Mix.Tasks.PrometheusTelemetry.Gen.Metrics do @shortdoc "Generates Metrics modules" @moduledoc """ This can be used to generate metrics modules The format used is ```elixir ::::tags:: ``` ### Example ```bash $ mix prometheus_telemetry.gen.metrics MyApp.Metrics.Type counter:event.name.measurement.count:event.name.count:count:tags:profile:region ``` The following metrics have been implemented: `counter`, `distribution`, `last_value` and `sum` With `distribution` you can also provide a second parameter of milliseconds, seconds or microseconds """ use Mix.Task def run([]) do Mix.raise("Must supply arguments to prometheus_telemetry.gen.metrics") end def run([metrics_module | measurements]) do measurements |> Enum.map(&(&1 |> String.split(":") |> parse_measurements)) |> build_metrics_module_from_measurements(metrics_module) |> write_metrics_file(metrics_module) end defp parse_measurements(["counter", metric_name, event_name, measurement | tags]) do [ type: :counter, metric_name: metric_name, event_name: parse_event_name(event_name), event_function: parse_function_name(event_name, measurement), measurement: String.to_atom(measurement), tags: parse_tags(tags) ] end defp parse_measurements(["distribution", "milliseconds", metric_name, event_name, measurement | tags]) do [ type: :distribution, metric_name: metric_name, event_name: parse_event_name(event_name), event_function: parse_function_name(event_name, measurement), measurement: String.to_atom(measurement), unit: :milliseconds, tags: parse_tags(tags) ] end defp parse_measurements(["distribution", "microseconds", metric_name, event_name, measurement | tags]) do [ type: :distribution, metric_name: metric_name, event_name: parse_event_name(event_name), event_function: parse_function_name(event_name, measurement), measurement: String.to_atom(measurement), unit: :microseconds, tags: parse_tags(tags) ] end defp parse_measurements(["distribution", "seconds", metric_name, event_name, measurement | tags]) do [ type: :distribution, metric_name: metric_name, event_name: parse_event_name(event_name), event_function: parse_function_name(event_name, measurement), measurement: String.to_atom(measurement), tags: parse_tags(tags) ] end defp parse_measurements(["distribution", metric_name, event_name, measurement | tags]) do [ type: :distribution, metric_name: metric_name, event_name: parse_event_name(event_name), event_function: parse_function_name(event_name, measurement), measurement: String.to_atom(measurement), unit: :milliseconds, tags: parse_tags(tags) ] end defp parse_measurements(["last_value", metric_name, event_name, measurement | tags]) do [ type: :last_value, metric_name: metric_name, event_name: parse_event_name(event_name), event_function: parse_function_name(event_name, measurement), measurement: String.to_atom(measurement), tags: parse_tags(tags) ] end defp parse_measurements(["sum", metric_name, event_name, measurement | tags]) do [ type: :sum, metric_name: metric_name, event_name: parse_event_name(event_name), event_function: parse_function_name(event_name, measurement), measurement: String.to_atom(measurement), tags: parse_tags(tags) ] end defp parse_tags(["tags" | tags]) when tags !== [] do Enum.map(tags, &String.to_atom/1) end defp parse_tags(_) do [] end defp parse_event_name(event_name) do event_name |> String.split(".") |> Enum.map(&String.to_atom/1) end defp parse_function_name(event_name, measurement) do event_name |> String.replace(".", "_") |> String.trim_trailing("_#{measurement}") |> String.trim_trailing(measurement) end defp build_metrics_module_from_measurements(measurements, metrics_module) do :prometheus_telemetry |> :code.priv_dir |> Path.join("metric_template.ex.eex") |> EEx.eval_file( assigns: %{ metrics: measurements, module_name: metrics_module, metrics_imports: measurement_metrics_imports(measurements) } ) |> Code.format_string! end defp measurement_metrics_imports(measurements) do measurements |> Enum.group_by(&(&1[:type])) |> Map.keys |> Enum.map(fn type -> {type, 2} end) end defp write_metrics_file(metrics_file_contents, metrics_module) do file_path = module_file_path(metrics_module) Mix.Generator.create_file(file_path, metrics_file_contents) end defp module_file_path(metrics_module) do metrics_path = metrics_module |> String.split(".") |> Enum.map(&Macro.underscore/1) |> then(&List.update_at(&1, length(&1) - 1, fn file_name -> "#{file_name}.ex" end)) Path.join(["lib" | metrics_path]) end end