Add Micrometer support for Task applications

- New dependencies
   - micrometer-core
   - java-cfenv - required by the CF tags configuration.
 - Add TaskMetrics configuration that installs two Meters (Timer and LongTaskTimer) that produce the following time-series:
   - spring.cloud.task - shows task duration (updated only after the task has completed).
   - spring.cloud.task.active - provides run time information about the non-completed tasks.
   Both meters are started at TaskExecutionListener#onTaskStartup() and ended at onTaskEnd() or onTaskFailure().
 - Add common Task tags assigned to all time-series produced by the Task (not only the s.c.task and s.c.task.active). This allows to analyze CPU, Memory and other measurements grouped by task/execution ids.
 - Add Cloud Found tags - Activated only for CF target platform.

 Resolves #608
This commit is contained in:
Christian Tzolov
2019-06-26 10:59:35 +02:00
committed by Michael Minella
parent aa29153656
commit d4b51a074b
14 changed files with 959 additions and 2 deletions

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2019-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.task.micrometer;
import java.io.IOException;
import java.nio.charset.Charset;
import io.micrometer.core.instrument.Clock;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.simple.SimpleConfig;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import io.pivotal.cfenv.test.CfEnvTestUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.StreamUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Christian Tzolov
* @author Soby Chacko
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = { AbstractMicrometerTest.AutoConfigurationApplication.class })
@DirtiesContext
public class AbstractMicrometerTest {
@Autowired
protected SimpleMeterRegistry simpleMeterRegistry;
@Autowired
protected ConfigurableApplicationContext context;
protected Meter meter;
@Before
public void before() {
Metrics.globalRegistry.getMeters().forEach(Metrics.globalRegistry::remove);
assertThat(simpleMeterRegistry).isNotNull();
meter = simpleMeterRegistry.find("spring.integration.handlers").meter();
assertThat(meter).isNotNull().withFailMessage(
"The spring.integration.handlers meter must be present in SpringBoot apps!");
}
@After
public void after() {
Metrics.globalRegistry.getMeters().forEach(Metrics.globalRegistry::remove);
}
@BeforeClass
public static void setup() throws IOException {
String serviceJson = StreamUtils.copyToString(new DefaultResourceLoader()
.getResource("classpath:/micrometer/pcf-scs-info.json").getInputStream(),
Charset.forName("UTF-8"));
CfEnvTestUtils.mockVcapServicesFromString(serviceJson);
}
@SpringBootApplication
public static class AutoConfigurationApplication {
public static void main(String[] args) {
SpringApplication.run(AutoConfigurationApplication.class, args);
}
@Bean
@ConditionalOnMissingBean
public SimpleMeterRegistry simpleMeterRegistry(SimpleConfig config, Clock clock) {
return new SimpleMeterRegistry(config, clock);
}
@Bean
@ConditionalOnMissingBean
public SimpleConfig simpleConfig() {
return key -> null;
}
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright 2019-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.task.micrometer;
import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Christian Tzolov
*/
@RunWith(Enclosed.class)
public class CloudFoundryMicrometerTagsConfigurationTest {
@ActiveProfiles("cloud")
public static class ActiveCloudProfileDefaultValues extends AbstractMicrometerTest {
@Test
public void testDefaultTagValues() {
assertThat(meter.getId().getTag("cf.org.name")).isEqualTo("default");
assertThat(meter.getId().getTag("cf.space.id")).isEqualTo("unknown");
assertThat(meter.getId().getTag("cf.space.name")).isEqualTo("unknown");
assertThat(meter.getId().getTag("cf.app.name")).isEqualTo("unknown");
assertThat(meter.getId().getTag("cf.app.id")).isEqualTo("unknown");
assertThat(meter.getId().getTag("cf.app.version")).isEqualTo("unknown");
assertThat(meter.getId().getTag("cf.instance.index")).isEqualTo("0");
}
}
@TestPropertySource(properties = { "vcap.application.org_name=PivotalOrg",
"vcap.application.space_id=SpringSpaceId",
"vcap.application.space_name=SpringSpace",
"vcap.application.application_name=App123",
"vcap.application.application_id=123guid",
"vcap.application.application_version=2.0",
"vcap.application.instance_index=123" })
@ActiveProfiles("cloud")
public static class ActiveCloudProfile extends AbstractMicrometerTest {
@Test
public void testPresetTagValues() {
assertThat(meter.getId().getTag("cf.org.name")).isEqualTo("PivotalOrg");
assertThat(meter.getId().getTag("cf.space.id")).isEqualTo("SpringSpaceId");
assertThat(meter.getId().getTag("cf.space.name")).isEqualTo("SpringSpace");
assertThat(meter.getId().getTag("cf.app.name")).isEqualTo("App123");
assertThat(meter.getId().getTag("cf.app.id")).isEqualTo("123guid");
assertThat(meter.getId().getTag("cf.app.version")).isEqualTo("2.0");
assertThat(meter.getId().getTag("cf.instance.index")).isEqualTo("123");
}
}
@TestPropertySource(properties = { "vcap.application.org_name=PivotalOrg",
"vcap.application.space_id=SpringSpaceId",
"vcap.application.space_name=SpringSpace",
"vcap.application.application_name=App123",
"vcap.application.application_id=123guid",
"vcap.application.application_version=2.0",
"vcap.application.instance_index=123" })
public static class InactiveCloudProfile extends AbstractMicrometerTest {
@Test
public void testDisabledTagValues() {
assertThat(meter.getId().getTag("cf.org.name")).isNull();
assertThat(meter.getId().getTag("cf.space.id")).isNull();
assertThat(meter.getId().getTag("cf.space.name")).isNull();
assertThat(meter.getId().getTag("cf.app.name")).isNull();
assertThat(meter.getId().getTag("cf.app.id")).isNull();
assertThat(meter.getId().getTag("cf.app.version")).isNull();
assertThat(meter.getId().getTag("cf.instance.index")).isNull();
}
}
@TestPropertySource(
properties = { "spring.cloud.task.metrics.cf.tags.enabled=false" })
@ActiveProfiles("cloud")
public static class ActiveCloudProfileDisabledProperty extends InactiveCloudProfile {
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2019-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.task.micrometer;
import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;
import org.springframework.test.context.TestPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Christian Tzolov
*/
@RunWith(Enclosed.class)
public class SpringCloudTaskMicrometerCommonTagsConfigurationTest {
public static class TestDefaultTagValues extends AbstractMicrometerTest {
@Test
public void testDefaultTagValues() {
assertThat(meter.getId().getTag("task.name")).isEqualTo("unknown");
assertThat(meter.getId().getTag("task.execution.id")).isEqualTo("unknown");
assertThat(meter.getId().getTag("task.parent.execution.id"))
.isEqualTo("unknown");
assertThat(meter.getId().getTag("task.external.execution.id"))
.isEqualTo("unknown");
}
}
@TestPropertySource(properties = { "spring.cloud.task.name=myTask",
"spring.cloud.task.executionid=123",
"spring.cloud.task.parent-execution-id=999",
"spring.cloud.task.external-execution-id=696" })
public static class TestPresetTagValues extends AbstractMicrometerTest {
@Test
public void testPresetTagValues() {
assertThat(meter.getId().getTag("task.name")).isEqualTo("myTask");
assertThat(meter.getId().getTag("task.execution.id")).isEqualTo("123");
assertThat(meter.getId().getTag("task.parent.execution.id")).isEqualTo("999");
assertThat(meter.getId().getTag("task.external.execution.id"))
.isEqualTo("696");
}
}
@TestPropertySource(
properties = { "spring.cloud.task.metrics.common.tags.enabled=false" })
public static class TestDisabledTagValues extends AbstractMicrometerTest {
@Test
public void testDefaultTagValues() {
assertThat(meter.getId().getTag("task.name")).isNull();
assertThat(meter.getId().getTag("task.execution.id")).isNull();
assertThat(meter.getId().getTag("task.parent.execution.id")).isNull();
assertThat(meter.getId().getTag("task.external.execution.id")).isNull();
}
}
}

View File

@@ -0,0 +1,168 @@
/*
* Copyright 2019-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.task.micrometer;
import java.util.ArrayList;
import java.util.Date;
import io.micrometer.core.instrument.LongTaskTimer;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.cloud.task.listener.TaskMetrics;
import org.springframework.cloud.task.repository.TaskExecution;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Christian Tzolov
*/
public class TaskMetricsTests {
private TaskMetrics taskMetrics;
private SimpleMeterRegistry simpleMeterRegistry;
@Before
public void before() {
Metrics.globalRegistry.getMeters().forEach(Metrics.globalRegistry::remove);
simpleMeterRegistry = new SimpleMeterRegistry();
Metrics.addRegistry(simpleMeterRegistry);
taskMetrics = new TaskMetrics();
}
@After
public void after() {
Metrics.globalRegistry.getMeters().forEach(Metrics.globalRegistry::remove);
}
@Test
public void successfulTask() {
TaskExecution taskExecution = new TaskExecution(123L, 0, "myTask72", new Date(),
new Date(), null, new ArrayList<>(), null, null, -1L);
// Start Task
taskMetrics.onTaskStartup(taskExecution);
//// Test Long Task Timer while the task is running.
LongTaskTimer longTaskTimer = simpleMeterRegistry
.find(TaskMetrics.SPRING_CLOUD_TASK_ACTIVE_METER).longTaskTimer();
assertThat(longTaskTimer)
.withFailMessage("LongTask timer should be created on Task start")
.isNotNull();
// assertThat(longTaskTimer.activeTasks()).isEqualTo(1);
assertThat(longTaskTimer.getId().getTag(TaskMetrics.TASK_NAME_TAG))
.isEqualTo("myTask72");
assertThat(longTaskTimer.getId().getTag(TaskMetrics.TASK_EXECUTION_ID_TAG))
.isEqualTo("123");
// Finish Task
taskMetrics.onTaskEnd(taskExecution);
// Test Timer
Timer taskTimer = simpleMeterRegistry.find(TaskMetrics.SPRING_CLOUD_TASK_METER)
.timer();
assertThat(taskTimer).isNotNull();
// assertThat(taskTimer.count()).isEqualTo(1L);
assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_NAME_TAG))
.isEqualTo("myTask72");
assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_EXECUTION_ID_TAG))
.isEqualTo("123");
assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_EXTERNAL_EXECUTION_ID_TAG))
.isEqualTo("unknown");
assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_PARENT_EXECUTION_ID_TAG))
.isEqualTo("-1");
assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_EXIT_CODE_TAG))
.isEqualTo("0");
assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_EXCEPTION_TAG))
.isEqualTo("none");
assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_STATUS_TAG))
.isEqualTo(TaskMetrics.STATUS_SUCCESS);
// Test Long Task Timer after the task has completed.
// LongTaskTimer longTaskTimer = simpleMeterRegistry
// .find(TaskMetrics.SPRING_CLOUD_TASK_ACTIVE_METER).longTaskTimer();
assertThat(longTaskTimer.activeTasks()).isEqualTo(0);
assertThat(longTaskTimer.getId().getTag(TaskMetrics.TASK_NAME_TAG))
.isEqualTo("myTask72");
assertThat(longTaskTimer.getId().getTag(TaskMetrics.TASK_EXECUTION_ID_TAG))
.isEqualTo("123");
}
@Test
public void failingTask() {
TaskExecution taskExecution = new TaskExecution(123L, 0, "myTask", new Date(),
new Date(), null, new ArrayList<>(), null, null, -1L);
// Start Task
taskMetrics.onTaskStartup(taskExecution);
// Test Long Task Timer while the task is running.
LongTaskTimer longTaskTimer = simpleMeterRegistry
.find(TaskMetrics.SPRING_CLOUD_TASK_ACTIVE_METER).longTaskTimer();
assertThat(longTaskTimer)
.withFailMessage("LongTask timer should be created on Task start")
.isNotNull();
assertThat(longTaskTimer.activeTasks()).isEqualTo(1);
assertThat(longTaskTimer.getId().getTag(TaskMetrics.TASK_NAME_TAG))
.isEqualTo("myTask");
assertThat(longTaskTimer.getId().getTag(TaskMetrics.TASK_EXECUTION_ID_TAG))
.isEqualTo("123");
taskMetrics.onTaskFailed(taskExecution, new RuntimeException("Test"));
// Finish Task. TaskLifecycleListen calls onTaskEnd after the onTaskFailed. Make
// sure that the counter status
// is not affected by this.
taskMetrics.onTaskEnd(taskExecution);
Timer taskTimer = simpleMeterRegistry.find(TaskMetrics.SPRING_CLOUD_TASK_METER)
.timer();
assertThat(taskTimer).isNotNull();
assertThat(taskTimer).isNotNull();
// assertThat(taskTimer.count()).isEqualTo(1L);
assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_NAME_TAG))
.isEqualTo("myTask");
assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_EXECUTION_ID_TAG))
.isEqualTo("123");
assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_EXTERNAL_EXECUTION_ID_TAG))
.isEqualTo("unknown");
assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_PARENT_EXECUTION_ID_TAG))
.isEqualTo("-1");
assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_EXIT_CODE_TAG))
.isEqualTo("0");
assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_EXCEPTION_TAG))
.isEqualTo("RuntimeException");
assertThat(taskTimer.getId().getTag(TaskMetrics.TASK_STATUS_TAG))
.isEqualTo(TaskMetrics.STATUS_FAILURE);
// Test Long Task Timer after the task has completed.
assertThat(longTaskTimer.activeTasks()).isEqualTo(0);
assertThat(longTaskTimer.getId().getTag(TaskMetrics.TASK_NAME_TAG))
.isEqualTo("myTask");
assertThat(longTaskTimer.getId().getTag(TaskMetrics.TASK_EXECUTION_ID_TAG))
.isEqualTo("123");
}
}

View File

@@ -0,0 +1,13 @@
{
"sso":[{
"name": "sso",
"label": "sso",
"plan": "notfree",
"tags": ["configuration"],
"credentials":{
"uri": "https://pivotal.io",
"client_id": "fakeClientId",
"client_secret": "fakeSecret",
"access_token_uri": "token"
}
}]}