Skip to content
This repository was archived by the owner on Jan 14, 2026. It is now read-only.

Commit e99edcf

Browse files
authored
Merge pull request #10 from zegelin/v94-wip
Version 0.9.4 changes
2 parents a731832 + 09f535f commit e99edcf

43 files changed

Lines changed: 1419 additions & 541 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 72 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
11
# cassandra-exporter [![CircleCI](https://circleci.com/gh/instaclustr/cassandra-exporter.svg?style=svg)](https://circleci.com/gh/instaclustr/cassandra-exporter)
22

3+
*cassandra-exporter* is a Java agent (with optional standalone mode) that exports Cassandra metrics to [Prometheus](http://prometheus.io).
4+
35
*Project Status: beta*
46

57

68
## Introduction
79

8-
*cassandra-exporter* is a Java agent (with optional standalone mode) that exports Cassandra metrics to [Prometheus](http://prometheus.io).
9-
10-
It enables high performance collection of Cassandra metrics and follows the Prometheus best practices for metrics naming and labeling.
10+
*cassandra-exporter* enables high performance collection of Cassandra metrics and follows the Prometheus best practices for metrics naming and labeling.
1111

1212
![Benchamrk Results](doc/benchmark-results.png)
1313

1414
*cassandra-exporter* is fast. In a worst-case benchmark, where the Cassandra schema contains 1000+ tables (resulting in ~174 thousand metrics),
1515
*cassandra-exporter* completes exposition in ~140ms. Compared to the next-best, *jmx_exporter*, which completes exposition in _~8 seconds_.
1616
Other solutions can take _tens of seconds_, during which CPU time is consumed querying JMX and serialising values.
1717

18-
There is no caching involved -- all metrics exposed by *cassandra-exporter* are live
19-
(except `cassandra_table_snapshots_size_total_bytes`, which is expensive to query).
18+
See the [Exported Metrics](https://github.com/instaclustr/cassandra-exporter/wiki/Exported-Metrics) wiki page for a list of available metrics.
19+
20+
All but a few select metrics exposed by *cassandra-exporter* are live with no caching involved.
21+
The few that are cached are done so for performance reasons.
22+
23+
24+
2025

2126
*cassandra-exporter* exports metric families, where the names, labels, metric types (gauge, counter, summary, etc), and value scales
2227
have been hand-tuned to produce easy-to-query output.
@@ -95,23 +100,46 @@ The format/structure of the JSON output is subject to change.
95100

96101
The available command line options may be seen by passing `-h`/`--help`:
97102

98-
Usage: cassandra-exporter-standalone [-hV] [--enable-per-thread-cpu-times]
103+
Usage: cassandra-exporter-standalone [-hV] [--enable-collector-timing]
104+
[--enable-per-thread-cpu-times]
105+
[--exclude-system-tables]
99106
[--no-fast-float] [--no-global-labels]
100-
[--family-help=VALUE]
107+
[--no-table-labels] [--cql-address=
108+
[ADDRESS][:PORT]]
109+
[--cql-password=PASSWORD]
110+
[--cql-user=NAME] [--family-help=VALUE]
101111
[--jmx-password=PASSWORD]
102112
[--jmx-service-url=URL] [--jmx-user=NAME]
103-
[-g=LABEL[,LABEL...]]... [-l=[ADDRESS][:
104-
PORT]]... [-e=EXCLUSION...]...
113+
[--exclude-keyspaces=<excludedKeyspaces>]..
114+
. [-g=LABEL[,LABEL...]]... [-l=[ADDRESS][:
115+
PORT]]... [-t=LABEL[,LABEL...]]...
116+
[-e=EXCLUSION...]...
105117
-g, --global-labels=LABEL[,LABEL...]
106118
Select which global labels to include on all exported
107-
metrics. Valid options are: 'cluster_name', 'host_id'
108-
(UUID of the node), 'node' (node endpoint IP address),
109-
'datacenter', 'rack'. The default is to include all
110-
global labels. To disable all global labels use
119+
metrics. Valid options are: 'CLUSTER' (cluster name),
120+
'HOST_ID' (UUID of the node), 'NODE' (node endpoint IP
121+
address), 'DATACENTER' (DC name), 'RACK' (rack name).
122+
The default is to include all global labels except
123+
HOST_ID. To disable all global labels use
111124
--no-global-labels.
125+
-t, --table-labels=LABEL[,LABEL...]
126+
Select which labels to include on table-level metrics.
127+
Valid options are: 'TABLE_TYPE' (table, view or
128+
index), 'INDEX_TYPE' (for indexes -- keys, composites
129+
or custom), 'INDEX_CLASS' (the index class name for
130+
custom indexes), 'COMPACTION_STRATEGY_CLASS' (for
131+
tables & views, compaction-related metrics only). The
132+
default is to include all table labels. To disable all
133+
table labels use --no-table-labels.
112134
--enable-per-thread-cpu-times
113135
Collect per-thread CPU times, where each thread gets its
114136
own time-series. (EXPERIMENTAL)
137+
--enable-collector-timing
138+
Record the cumulative time taken to run each collector
139+
and export the results.
140+
--exclude-keyspaces=<excludedKeyspaces>
141+
142+
--no-global-labels Disable all global labels.
115143
-e, --exclude=EXCLUSION...
116144
Exclude a metric family or MBean from exposition.
117145
EXCLUSION may be the full name of a metric family
@@ -125,8 +153,10 @@ The available command line options may be seen by passing `-h`/`--help`:
125153
prefixed with '#' are considered comments and are
126154
ignored. This option may be specified more than once
127155
to define multiple exclusions.
156+
--no-table-labels Disable all table labels.
128157
--no-fast-float Disable the use of fast float -> ascii conversion.
129-
--no-global-labels Disable all global labels.
158+
--exclude-system-tables
159+
Exclude system table/keyspace metrics.
130160
-l, --listen=[ADDRESS][:PORT]
131161
Listen address (and optional port). ADDRESS may be a
132162
hostname, IPv4 dotted or decimal address, or IPv6
@@ -146,16 +176,23 @@ The available command line options may be seen by passing `-h`/`--help`:
146176
bandwidth. Can be overridden with the "?
147177
help=true|false" URI query parameter. Valid values:
148178
INCLUDE, EXCLUDE, AUTOMATIC. Defaults to AUTOMATIC.
149-
--jmx-service-url=URL The JMX service URL of the Cassandra instance to connect
150-
to and collect metrics. Defaults to 'service:jmx:rmi:
179+
--jmx-service-url=URL JMX service URL of the Cassandra instance to connect to
180+
and collect metrics. Defaults to 'service:jmx:rmi:
151181
///jndi/rmi://localhost:7199/jmxrmi'
152-
--jmx-user=NAME The JMX authentication user name.
182+
--jmx-user=NAME JMX authentication user name.
153183
--jmx-password=PASSWORD
154-
The JMX authentication password.
184+
JMX authentication password.
185+
--cql-address=[ADDRESS][:PORT]
186+
Address/hostname and optional port for the CQL metadata
187+
connection. Defaults to 'localhost:9042'
188+
--cql-user=NAME CQL authentication user name.
189+
--cql-password=PASSWORD
190+
CQL authentication password.
155191
-h, --help Show this help message and exit.
156192
-V, --version Print version information and exit.
157193

158194

195+
159196
Options may also be provided via an `@`-file:
160197

161198
- *Standalone*
@@ -168,7 +205,9 @@ Options may also be provided via an `@`-file:
168205
169206
`@$CASSANDRA_CONF/cassandra-exporter.options` is a good choice.
170207

171-
Note that `--jmx-service-url`, `--jmx-user` and `--jmx-password` are only applicable to the standalone version -- the agent does not use JMX.
208+
Note that `--jmx-service-url`, `--jmx-user`, `--jmx-password`, `--cql-address`, `--cql-user` and `--cql-password`
209+
are only applicable to the standalone version -- the agent does not use JMX or CQL connections.
210+
172211
To protect the JMX password and prevent it from showing up in `ps`, `top` and other utilities, use an `@`-file that contains `--jmx-password=PASSWORD`.
173212

174213
When run as an agent, command line options must be provided as part of the `-javaagent` flag, with an equals sign (`=`) separating the JAR path and the agent options.
@@ -213,11 +252,13 @@ For exporters that run as a separate process there is additional overhead of int
213252

214253
### Best practices
215254

216-
The exporter follows Prometheus best practices for metric names, labels and data types.
255+
The exporter attempts to follow Prometheus' best practices for metric names, labels and data types.
217256

218-
Aggregate metrics, such as the aggregated table metrics at the keyspace and node level, are skipped. Instead these should be aggregated using PromQL queries or Prometheus recording rules.
257+
Cassandras built-in aggregate metrics, such as the table-related metrics at the keyspace and node level, are skipped.
258+
Instead only the table-level metrics are exported &mdash; aggregates can be computed on-the-fly using PromQL queries or once using Prometheus recording rules.
219259

220-
Metrics are coalesced when appropriate so they share the same name, opting for *labels* to differentiate indiviual time series. For example, each table level metric has a constant name and at minimum a `table` & `keyspace` label, which allows for complex PromQL queries.
260+
Unlike the metrics exported via JMX, where each table metric has a unique name, Cassandras metrics are coalesced when appropriate so they share the same exported metric family name, opting for *labels* to differentiate individual time series.
261+
For example, each table level metric has a constant name and at minimum a `table` & `keyspace` label, which allows for complex PromQL queries.
221262

222263
For example the `cassandra_table_operation_latency_seconds[_count|_sum]` summary metric combines read, write, range read, CAS prepare, CAS propose and CAS commit latency metrics together into a single metric family.
223264
A summary exposes percentiles (via the `quantile` label), a total count of recorded samples (via the `_count` metric),
@@ -256,33 +297,33 @@ Element | Value
256297
### Global Labels
257298

258299
The exporter does attach global labels to the exported metrics.
259-
These may be configured with the `--global-labels` CLI option.
300+
These may be configured with the `--global-labels` (or disabled via `--no-global-labels) CLI option.
260301

261302
These labels are:
262303

263-
- `cassandra_cluster_name`
304+
- `cassandra_cluster`
264305

265-
The name of the cluster, as specified in cassandra.yaml
306+
The name of the cluster, as specified in cassandra.yaml.
266307

267308
- `cassandra_host_id`
268309

269-
The unique UUID of the node
310+
The unique UUID of the node. _Not enabled by default_
270311

271312
- `cassandra_node`
272313

273-
The IP address of the node
314+
The IP address of the node.
274315

275316
- `cassandra_datacenter`
276317

277-
The configured data center name of the node
318+
The configured data center name of the node.
278319

279320
- `cassandra_rack`
280321

281-
The configured rack name of the node
322+
The configured rack name of the node.
282323

283324
These labels allow aggregation of metrics at the cluster, data center and rack levels.
284325

285-
While these labels could be defined in the prometheus scrape config, we feel that having these labels be automatically
326+
While these labels could be defined in the Prometheus scrape config, we feel that having these labels be automatically
286327
applied simplifies things, especially when Prometheus is monitoring multiple clusters across numerous DCs and racks.
287328

288329

@@ -303,13 +344,12 @@ See the [project issue tracker](https://github.com/instaclustr/cassandra-exporte
303344
Early versions supported outputting metrics as a HTML document for easier viewing in a browser.
304345

305346
The format writer was complicated and we didn't want to add dependencies on a templating library (e.g. Freemarker) to make it simpler.
306-
Instead the JSON format writer has been improved and optimized with the intent that the data could be consumed by simple static Javascript webapp.
347+
Instead the JSON format writer has been improved and optimized with the intent that the data could be consumed by simple static JavaScript webapp.
307348

308349
- Add some example queries
309350
- Add Grafana dashboard templates
310351
- Documentation improvements
311352
- Improve standalone JMX exporter
312-
- Metadata support
313353
- Systemd service file
314354
- Package
315355

agent/src/main/java/com/zegelin/prometheus/cassandra/Agent.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ public static void premain(final String agentArgs, final Instrumentation instrum
4242

4343
final CommandLine commandLine = new CommandLine(new Agent());
4444

45+
commandLine.setCaseInsensitiveEnumValuesAllowed(true);
46+
4547
commandLine.parseWithHandlers(
4648
new CommandLine.RunLast(),
4749
CommandLine.defaultExceptionHandler().andExit(1),
Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package com.zegelin.prometheus.cassandra;
22

33
import org.apache.cassandra.config.CFMetaData;
4+
import org.apache.cassandra.config.DatabaseDescriptor;
45
import org.apache.cassandra.config.Schema;
6+
import org.apache.cassandra.locator.IEndpointSnitch;
7+
import org.apache.cassandra.utils.FBUtilities;
58

9+
import java.net.InetAddress;
610
import java.util.Optional;
7-
import java.util.UUID;
11+
import java.util.Set;
812

9-
10-
public class InternalMetadataFactory implements MetadataFactory {
13+
public class InternalMetadataFactory extends MetadataFactory {
1114
private static Optional<CFMetaData> getCFMetaData(final String keyspaceName, final String tableName) {
1215
return Optional.ofNullable(Schema.instance.getCFMetaData(keyspaceName, tableName));
1316
}
@@ -26,11 +29,6 @@ public IndexType indexType() {
2629
return indexType;
2730
}
2831

29-
@Override
30-
public UUID id() {
31-
return m.id;
32-
}
33-
3432
@Override
3533
public Optional<String> customClassName() {
3634
return className;
@@ -40,20 +38,50 @@ public Optional<String> customClassName() {
4038
}
4139

4240
@Override
43-
public Optional<TableMetadata> tableOrViewMetadata(final String keyspaceName, final String tableName) {
44-
return getCFMetaData(keyspaceName, tableName)
45-
.map(m -> {
46-
return new TableMetadata() {
47-
@Override
48-
public UUID id() {
49-
return m.cfId;
50-
}
41+
public Optional<TableMetadata> tableOrViewMetadata(final String keyspaceName, final String tableOrViewName) {
42+
return getCFMetaData(keyspaceName, tableOrViewName)
43+
.map(m -> new TableMetadata() {
44+
@Override
45+
public String compactionStrategyClassName() {
46+
return m.params.compaction.klass().getCanonicalName();
47+
}
5148

52-
@Override
53-
public boolean isView() {
54-
return m.isView();
55-
}
56-
};
49+
@Override
50+
public boolean isView() {
51+
return m.isView();
52+
}
5753
});
5854
}
55+
56+
@Override
57+
public Set<String> keyspaces() {
58+
return Schema.instance.getKeyspaces();
59+
}
60+
61+
@Override
62+
public Optional<EndpointMetadata> endpointMetadata(final InetAddress endpoint) {
63+
final IEndpointSnitch endpointSnitch = DatabaseDescriptor.getEndpointSnitch();
64+
65+
return Optional.of(new EndpointMetadata() {
66+
@Override
67+
public String dataCenter() {
68+
return endpointSnitch.getDatacenter(endpoint);
69+
}
70+
71+
@Override
72+
public String rack() {
73+
return endpointSnitch.getRack(endpoint);
74+
}
75+
});
76+
}
77+
78+
@Override
79+
public String clusterName() {
80+
return DatabaseDescriptor.getClusterName();
81+
}
82+
83+
@Override
84+
public InetAddress localBroadcastAddress() {
85+
return FBUtilities.getBroadcastAddress();
86+
}
5987
}

agent/src/main/java/com/zegelin/prometheus/cassandra/MBeanServerInterceptorHarvester.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
package com.zegelin.prometheus.cassandra;
22

3-
import com.google.common.collect.ImmutableList;
43
import com.sun.jmx.mbeanserver.JmxMBeanServer;
54
import com.zegelin.jmx.DelegatingMBeanServerInterceptor;
65
import com.zegelin.prometheus.cassandra.cli.HarvesterOptions;
6+
import com.zegelin.prometheus.cassandra.collector.InternalGossiperMBeanMetricFamilyCollector;
77

88
import javax.management.*;
99
import java.lang.management.ManagementFactory;
10-
import java.lang.management.PlatformManagedObject;
11-
import java.util.List;
12-
import java.util.stream.Collectors;
1310

1411
class MBeanServerInterceptorHarvester extends Harvester {
1512
class MBeanServerInterceptor extends DelegatingMBeanServerInterceptor {
@@ -42,21 +39,31 @@ public void unregisterMBean(final ObjectName mBeanName) throws InstanceNotFoundE
4239
}
4340

4441
MBeanServerInterceptorHarvester(final HarvesterOptions options) {
45-
super(new InternalMetadataFactory(), options);
42+
this(new InternalMetadataFactory(), options);
43+
}
44+
45+
private MBeanServerInterceptorHarvester(final MetadataFactory metadataFactory, final HarvesterOptions options) {
46+
super(metadataFactory, options);
4647

4748
registerPlatformMXBeans();
48-
registerMBeanServerInterceptor();
4949

50+
installMBeanServerInterceptor();
51+
52+
addCollectorFactory(InternalGossiperMBeanMetricFamilyCollector.factory(metadataFactory));
5053
}
5154

55+
5256
private void registerPlatformMXBeans() {
57+
// the platform MXBeans get registered right at JVM startup, before the agent gets a chance to
58+
// install the interceptor.
59+
// instead, directly register the MXBeans here...
5360
ManagementFactory.getPlatformManagementInterfaces().stream()
5461
.flatMap(i -> ManagementFactory.getPlatformMXBeans(i).stream())
5562
.distinct()
5663
.forEach(mxBean -> registerMBean(mxBean, mxBean.getObjectName()));
5764
}
5865

59-
private void registerMBeanServerInterceptor() {
66+
private void installMBeanServerInterceptor() {
6067
final JmxMBeanServer mBeanServer = (JmxMBeanServer) ManagementFactory.getPlatformMBeanServer();
6168

6269
final MBeanServerInterceptor interceptor = new MBeanServerInterceptor(mBeanServer.getMBeanServerInterceptor());

0 commit comments

Comments
 (0)