content: /*
   * Inkless
   * Copyright (C) 2024 - 2025 Aiven OY
   *
   * This program is free software: you can redistribute it and/or modify
   * it under the terms of the GNU Affero General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   *
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU Affero General Public License for more details.
   *
   * You should have received a copy of the GNU Affero General Public License
   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
   */
  
  package io.aiven.inkless.doc;
  
  import org.apache.kafka.common.MetricName;
  import org.apache.kafka.common.MetricNameTemplate;
  import org.apache.kafka.common.metrics.Metrics;
  import org.apache.kafka.common.utils.Sanitizer;
  
  import java.util.LinkedHashMap;
  import java.util.Map;
  import java.util.TreeMap;
  
  import static java.lang.System.out;
  
  public class MetricsDocs {
      public static void main(final String[] args) {
          printDocumentHeading("Diskless metrics");
  
          out.println(".. Generated by " + MetricsDocs.class.getCanonicalName());
          out.println();
  
          // AWS S3 storage backend metrics
          printHeading(io.aiven.inkless.storage_backend.s3.S3Storage.class.getSimpleName() + " metrics");
          out.println();
          out.println(toRstTable(io.aiven.inkless.storage_backend.s3.MetricRegistry.METRIC_CONTEXT, io.aiven.inkless.storage_backend.s3.MetricRegistry.all()));
  
          // Azure storage backend metrics
          printHeading(io.aiven.inkless.storage_backend.azure.AzureBlobStorage.class.getSimpleName() + " metrics");
          out.println();
          out.println(toRstTable(io.aiven.inkless.storage_backend.azure.MetricRegistry.METRIC_CONTEXT, io.aiven.inkless.storage_backend.azure.MetricRegistry.all()));
  
          // Google Cloud Storage backend metrics
          printHeading(io.aiven.inkless.storage_backend.gcs.GcsStorage.class.getSimpleName() + " metrics");
          out.println();
          out.println(toRstTable(io.aiven.inkless.storage_backend.gcs.MetricRegistry.METRIC_CONTEXT, io.aiven.inkless.storage_backend.gcs.MetricRegistry.all()));
      }
  
      // o.a.k.common.metrics.Metrics does only have generation of Html documentation.
      // as there is no plans to publish HTML docs, this util method is added to generate RST.
      // may be upstreamed.
      static String toRstTable(final String domain, final Iterable<MetricNameTemplate> allMetrics) {
          final Map<String, Map<String, String>> beansAndAttributes = new TreeMap<>();
  
          try (final Metrics metrics = new Metrics()) {
              for (final MetricNameTemplate template : allMetrics) {
                  final Map<String, String> tags = new LinkedHashMap<>();
                  for (final String s : template.tags()) {
                      tags.put(s, "{" + s + "}");
                  }
  
                  final MetricName metricName = metrics.metricName(
                          template.name(),
                          template.group(),
                          template.description(),
                          tags
                  );
                  final String beanName = getMBeanName(domain, metricName);
                  beansAndAttributes.computeIfAbsent(beanName, k -> new TreeMap<>());
                  final Map<String, String> attrAndDesc = beansAndAttributes.get(beanName);
                  if (!attrAndDesc.containsKey(template.name())) {
                      attrAndDesc.put(template.name(), template.description());
                  } else {
                      throw new IllegalArgumentException(
                              "mBean '" + beanName
                                      + "' attribute '"
                                      + template.name()
                                      + "' is defined twice."
                      );
                  }
              }
          }
  
          final StringBuilder b = new StringBuilder();
  
          for (final Map.Entry<String, Map<String, String>> e : beansAndAttributes.entrySet()) {
              // Add mBean name as a section title
              b.append(e.getKey()).append("\n");
              b.append("-".repeat(e.getKey().length())).append("\n\n");
  
              // Determine the maximum lengths for each column
              final int maxAttrLength = Math.max("Attribute name".length(),
                      e.getValue().keySet().stream().mapToInt(String::length).max().orElse(0));
              final int maxDescLength = Math.max("Description".length(),
                      e.getValue().values().stream().mapToInt(String::length).max().orElse(0));
  
              // Create the table header
              final String headerFormat = "%-" + maxAttrLength + "s   %-" + maxDescLength + "s\n";
              final String separatorLine = "=" + "=".repeat(maxAttrLength) + "  " + "=".repeat(maxDescLength) + "\n";
  
              b.append(separatorLine);
              b.append(String.format(headerFormat, "Attribute name", "Description"));
              b.append(separatorLine);
  
              // Add table rows
              for (final Map.Entry<String, String> e2 : e.getValue().entrySet()) {
                  b.append(String.format(headerFormat, e2.getKey(), e2.getValue()));
              }
  
              // Close the table
              b.append(separatorLine);
              b.append("\n");  // Add an empty line between tables
          }
  
          return b.toString();
      }
  
      static void printDocumentHeading(final String title) {
          out.println("=================\n" + title + "\n" + "=================");
      }
  
      static void printHeading(final String title) {
          out.println(title + "\n" + "==================================");
      }
  
      /**
       * {@link org.apache.kafka.common.metrics.JmxReporter#getMBeanName(String, MetricName)}
       */
      static String getMBeanName(String prefix, MetricName metricName) {
          StringBuilder mBeanName = new StringBuilder();
          mBeanName.append(prefix);
          mBeanName.append(":type=");
          mBeanName.append(metricName.group());
          for (Map.Entry<String, String> entry : metricName.tags().entrySet()) {
              if (entry.getKey().isEmpty() || entry.getValue().isEmpty())
                  continue;
              mBeanName.append(",");
              mBeanName.append(entry.getKey());
              mBeanName.append("=");
              mBeanName.append(Sanitizer.jmxSanitize(entry.getValue()));
          }
          return mBeanName.toString();
      }
  }