/** * Remove a sensor (if it exists), associated metrics and its children. * * @param name The name of the sensor to be removed */ public void removeSensor(String name) { Sensor sensor = sensors.get(name); if (sensor != null) { List<Sensor> childSensors = null; synchronized (sensor) { synchronized (this) { if (sensors.remove(name, sensor)) { for (KafkaMetric metric : sensor.metrics()) removeMetric(metric.metricName()); log.debug("Removed sensor with name {}", name); childSensors = childrenSensors.remove(sensor); for (final Sensor parent : sensor.parents()) { childrenSensors.getOrDefault(parent, emptyList()).remove(sensor); } } } } if (childSensors != null) { for (Sensor childSensor : childSensors) removeSensor(childSensor.name()); } } }
/** * Verifies that concurrent sensor add, remove, updates and read don't result * in errors or deadlock. */ @Test public void testConcurrentReadUpdate() throws Exception { final Random random = new Random(); final Deque<Sensor> sensors = new ConcurrentLinkedDeque<>(); metrics = new Metrics(new MockTime(10)); SensorCreator sensorCreator = new SensorCreator(metrics); final AtomicBoolean alive = new AtomicBoolean(true); executorService = Executors.newSingleThreadExecutor(); executorService.submit(new ConcurrentMetricOperation(alive, "record", () -> sensors.forEach(sensor -> sensor.record(random.nextInt(10000))))); for (int i = 0; i < 10000; i++) { if (sensors.size() > 5) { Sensor sensor = random.nextBoolean() ? sensors.removeFirst() : sensors.removeLast(); metrics.removeSensor(sensor.name()); } StatType statType = StatType.forId(random.nextInt(StatType.values().length)); sensors.add(sensorCreator.createSensor(statType, i)); for (Sensor sensor : sensors) { for (KafkaMetric metric : sensor.metrics()) { assertNotNull("Invalid metric value", metric.metricValue()); } } } alive.set(false); }
() -> sensors.forEach(sensor -> sensor.record(random.nextInt(10000))))); Future<?> readFuture = executorService.submit(new ConcurrentMetricOperation(alive, "read", () -> sensors.forEach(sensor -> sensor.metrics().forEach(metric -> assertNotNull("Invalid metric value", metric.metricValue()))))); Future<?> reportFuture = executorService.submit(new ConcurrentMetricOperation(alive, "report",
grandchild.record(); double p1 = (double) parent1.metrics().get(0).metricValue(); double p2 = (double) parent2.metrics().get(0).metricValue(); double c1 = (double) child1.metrics().get(0).metricValue(); double c2 = (double) child2.metrics().get(0).metricValue(); double gc = (double) grandchild.metrics().get(0).metricValue();
@Test public void testIdempotentAdd() { final Metrics metrics = new Metrics(); final Sensor sensor = metrics.sensor("sensor"); assertTrue(sensor.add(metrics.metricName("test-metric", "test-group"), new Avg())); // adding the same metric to the same sensor is a no-op assertTrue(sensor.add(metrics.metricName("test-metric", "test-group"), new Avg())); // but adding the same metric to a DIFFERENT sensor is an error final Sensor anotherSensor = metrics.sensor("another-sensor"); try { anotherSensor.add(metrics.metricName("test-metric", "test-group"), new Avg()); fail("should have thrown"); } catch (final IllegalArgumentException ignored) { // pass } // note that adding a different metric with the same name is also a no-op assertTrue(sensor.add(metrics.metricName("test-metric", "test-group"), new Sum())); // so after all this, we still just have the original metric registered assertEquals(1, sensor.metrics().size()); assertEquals(org.apache.kafka.common.metrics.stats.Avg.class, sensor.metrics().get(0).measurable().getClass()); }