Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2494,6 +2494,19 @@ project(':storage:inkless') {
}
}

task genInklessMetricsDoc(type: JavaExec) {
classpath = sourceSets.main.runtimeClasspath
mainClass = 'io.aiven.inkless.doc.MetricsDocs'

// Define the outputs formally
outputs.file("$rootDir/docs/inkless/metrics.rst")

// Set up the output in the execution phase, not configuration, and avoid removing on clean
doFirst {
standardOutput = new File("$rootDir/docs/inkless/metrics.rst").newOutputStream()
}
}

tasks {
generateJooqClasses {
withContainer {
Expand Down Expand Up @@ -2603,6 +2616,7 @@ project(':storage:inkless') {
tasks.named('build') {
dependsOn tasks.named('genInklessConfigDoc')
dependsOn tasks.named('genInklessTopicConfigDoc')
dependsOn tasks.named('genInklessMetricsDoc')
}
}

Expand Down
2 changes: 2 additions & 0 deletions docs/inkless/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ For configuration references, see [Configs](configs.rst)

For a Topic Config documentation, see [Topic Configs](topic_configs.rst)

For metrics documentation, see [Metrics](metrics.rst)

Inkless is not supposed to be a long-term fork. We don't accept patches. The code is open exclusively for information purposes. We actively work on contributing these changes to Apache Kafka, see [KIP-1150: Diskless Topics](https://cwiki.apache.org/confluence/display/KAFKA/KIP-1150%3A+Diskless+Topics).
118 changes: 118 additions & 0 deletions docs/inkless/metrics.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
=================
Inkless metrics
=================
.. Generated by io.aiven.inkless.doc.MetricsDocs

Writer metrics
==================================

io.aiven.inkless.produce:type=io.aiven.inkless.produce
------------------------------------------------------

=============== =================================================
Attribute name Description
=============== =================================================
RequestRate The number of write requests per unit time.
RotationRate The number of file rotations per unit time.
RotationTime The time taken to rotate a file, in milliseconds.
=============== =================================================


S3Storage metrics
==================================

aiven.inkless.server.s3:type=s3-client-metrics
----------------------------------------------

========================================= =============================================================================
Attribute name Description
========================================= =============================================================================
abort-multipart-upload-requests-rate Rate of abort multi-part upload operations
abort-multipart-upload-requests-total Total number of abort multi-part upload operations
abort-multipart-upload-time-avg Average time spent aborting a new multi-part upload operation
abort-multipart-upload-time-max Maximum time spent aborting a new multi-part upload operation
complete-multipart-upload-requests-rate Rate of complete multi-part upload operations
complete-multipart-upload-requests-total Total number of complete multi-part upload operations
complete-multipart-upload-time-avg Average time spent completing a new multi-part upload operation
complete-multipart-upload-time-max Maximum time spent completing a new multi-part upload operation
configured-timeout-errors-rate Rate of configured timeout errors
configured-timeout-errors-total Total number of configured timeout errors
create-multipart-upload-requests-rate Rate of create multi-part upload operations
create-multipart-upload-requests-total Total number of create multi-part upload operations
create-multipart-upload-time-avg Average time spent creating a new multi-part upload operation
create-multipart-upload-time-max Maximum time spent creating a new multi-part upload operation
delete-object-requests-rate Rate of delete object request operations
delete-object-requests-total Total number of delete object request operations
delete-object-time-avg Average time spent deleting an object
delete-object-time-max Maximum time spent deleting an object
delete-objects-requests-rate Rate of delete a set of objects request operations
delete-objects-requests-total Total number of delete a set of objects request operations
delete-objects-time-avg Average time spent deleting a set of objects
delete-objects-time-max Maximum time spent deleting a set of objects
get-object-requests-rate Rate of get object request operations
get-object-requests-total Total number of get object request operations
get-object-time-avg Average time spent getting a response from a get object request
get-object-time-max Maximum time spent getting a response from a get object request
io-errors-rate Rate of IO errors
io-errors-total Total number of IO errors
other-errors-rate Rate of other errors
other-errors-total Total number of other errors
put-object-requests-rate Rate of put object request operations
put-object-requests-total Total number of put object request operations
put-object-time-avg Average time spent uploading an object
put-object-time-max Maximum time spent uploading an object
server-errors-rate Rate of server errors
server-errors-total Total number of server errors
throttling-errors-rate Rate of throttling errors
throttling-errors-total Total number of throttling errors
upload-part-requests-rate Rate of upload part request operations (as part of multi-part upload)
upload-part-requests-total Total number of upload part request operations (as part of multi-part upload)
upload-part-time-avg Average time spent uploading a single part
upload-part-time-max Maximum time spent uploading a single part
========================================= =============================================================================


AzureBlobStorage metrics
==================================

io.aiven.inkless.storage.azure:type=azure-blob-storage-client-metrics
---------------------------------------------------------------------

======================== ============================================================
Attribute name Description
======================== ============================================================
blob-delete-rate Rate of object delete operations
blob-delete-total Total number of object delete operations
blob-get-rate Rate of get object operations
blob-get-total Total number of get object operations
blob-upload-rate Rate of object upload operations
blob-upload-total Total number of object upload operations
block-list-upload-rate Rate of block list (making a blob) upload operations
block-list-upload-total Total number of block list (making a blob) upload operations
block-upload-rate Rate of block (blob part) upload operations
block-upload-total Total number of block (blob part) upload operations
======================== ============================================================


GcsStorage metrics
==================================

io.aiven.inkless.storage.gcs:type=gcs-client-metrics
----------------------------------------------------

================================ ===================================================================
Attribute name Description
================================ ===================================================================
object-delete-rate Rate of delete object operations
object-delete-total Total number of delete object operations
object-get-rate Rate of get object operations
object-get-total Total number of get object operations
object-metadata-get-rate Rate of get object metadata operations
object-metadata-get-total Total number of get object metadata operations
resumable-chunk-upload-rate Rate of upload chunk operations as part of resumable upload
resumable-chunk-upload-total Total number of upload chunk operations as part of resumable upload
resumable-upload-initiate-rate Rate of initiate resumable upload operations
resumable-upload-initiate-total Total number of initiate resumable upload operations
================================ ===================================================================


Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ public final Histogram newHistogram(String name, boolean biased, Map<String, Str
return KafkaYammerMetrics.defaultRegistry().newHistogram(metricName(name, tags), biased);
}

public final Histogram newHistogram(MetricName metricName, boolean biased, Map<String, String> tags) {
return KafkaYammerMetrics.defaultRegistry().newHistogram(metricName, biased);
}

public final Histogram newHistogram(String name) {
return newHistogram(name, true, Map.of());
}
Expand Down Expand Up @@ -172,3 +176,4 @@ private static Optional<String> toScope(Map<String, String> tags) {
}
}
}

159 changes: 159 additions & 0 deletions storage/inkless/src/main/java/io/aiven/inkless/doc/MetricsDocs.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
* 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 io.aiven.inkless.produce.WriterMetrics;
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 org.apache.kafka.common.utils.Time;

import java.io.IOException;
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) throws IOException {
printDocumentHeading("Inkless metrics");

out.println(".. Generated by " + MetricsDocs.class.getCanonicalName());
out.println();

printHeading(io.aiven.inkless.produce.Writer.class.getSimpleName() + " metrics");
try (WriterMetrics writerMetrics = new WriterMetrics(Time.SYSTEM)) {
out.println();
out.println(toRstTable(io.aiven.inkless.produce.WriterMetrics.class.getPackageName(), writerMetrics.all()));
}

// 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
*
* <p>The class is thread-safe: all the event entry points are protected with the lock.</p>
*/
class Writer implements Closeable {
public class Writer implements Closeable {
private static final Logger LOGGER = LoggerFactory.getLogger(Writer.class);

private final Lock lock = new ReentrantLock();
Expand Down
Loading