Skip to content

Commit

Permalink
Performance Feature (#971)
Browse files Browse the repository at this point in the history
  • Loading branch information
maciejwalkowiak committed Nov 20, 2020
1 parent 8e49958 commit 3c87a48
Show file tree
Hide file tree
Showing 107 changed files with 4,058 additions and 319 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
* Fix broken NDK integration on 3.1.2 (release failed on packaging a .so file)
* Increase max cached events to 30 (#1029)
* Normalize DSN URI (#1030)
* feat: Performance monitoring (#971)
* feat: Performance monitoring for Spring Boot applications

# 3.1.2

Expand Down
3 changes: 3 additions & 0 deletions buildSrc/src/main/java/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,12 @@ object Config {
val springBootStarter = "org.springframework.boot:spring-boot-starter:$springBootVersion"
val springBootStarterTest = "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
val springBootStarterWeb = "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
val springBootStarterAop = "org.springframework.boot:spring-boot-starter-aop:$springBootVersion"
val springBootStarterSecurity = "org.springframework.boot:spring-boot-starter-security:$springBootVersion"

val springWeb = "org.springframework:spring-webmvc"
val springAop = "org.springframework:spring-aop"
val aspectj = "org.aspectj:aspectjweaver"
val servletApi = "javax.servlet:javax.servlet-api"
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import android.util.Log;
import io.sentry.ILogger;
import io.sentry.SentryLevel;
import org.jetbrains.annotations.Nullable;

final class AndroidLogger implements ILogger {

Expand Down Expand Up @@ -43,6 +44,11 @@ public void log(SentryLevel level, String message, Throwable throwable) {
}
}

@Override
public boolean isEnabled(@Nullable SentryLevel level) {
return true;
}

private int toLogcatLevel(SentryLevel sentryLevel) {
switch (sentryLevel) {
case INFO:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import android.os.Bundle
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.nhaarman.mockitokotlin2.mock
import io.sentry.ILogger
import io.sentry.InvalidDsnException
import io.sentry.Sentry
import io.sentry.exception.InvalidDsnException
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertFailsWith
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@
import io.sentry.Sentry;
import io.sentry.SentryEvent;
import io.sentry.SentryLevel;
import io.sentry.SentryTransaction;
import io.sentry.Span;
import io.sentry.SpanStatus;
import io.sentry.protocol.Message;
import io.sentry.protocol.User;
import java.util.Collections;

public class Main {

public static void main(String[] args) {
public static void main(String[] args) throws InterruptedException {
Sentry.init(
options -> {
// NOTE: Replace the test DSN below with YOUR OWN DSN to see the events from this app in
Expand Down Expand Up @@ -60,6 +63,21 @@ public static void main(String[] args) {
// Exclude frames from some packages from being "inApp" so are hidden by default in Sentry
// UI:
options.addInAppExclude("org.jboss");

// Performance configuration options
// Set what percentage of traces should be collected
options.setTracesSampleRate(1.0); // set 0.5 to send 50% of traces

// Determine traces sample rate based on the sampling context
options.setTracesSampler(
context -> {
// only 10% of transactions with "/product" prefix will be collected
if (!context.getTransactionContexts().getName().startsWith("/products")) {
return 0.1;
} else {
return 0.5;
}
});
});

Sentry.addBreadcrumb(
Expand Down Expand Up @@ -116,6 +134,30 @@ public static void main(String[] args) {
Sentry.captureEvent(event, SentryLevel.DEBUG);
}

// Performance feature
//
// Transactions collect execution time of the piece of code that's executed between the start
// and finish of transaction.
SentryTransaction transaction = Sentry.startTransaction("transaction name");
// Transactions can contain one or more Spans
Span outerSpan = transaction.startChild();
Thread.sleep(100);
// Spans create a tree structure. Each span can have one ore more spans inside.
Span innerSpan = outerSpan.startChild();
innerSpan.setOperation("jdbc");
innerSpan.setDescription("select * from product where id = :id");
innerSpan.setStatus(SpanStatus.OK);
Thread.sleep(300);
// Finish the span and mark the end time of the span execution.
// Note: finishing spans does not sent them to Sentry
innerSpan.finish();
// Every SentryEvent reported during the execution of the transaction or a span, will have trace
// context attached
Sentry.captureMessage("this message is connected to the outerSpan");
outerSpan.finish();
// marks transaction as finished and sends it together with all child spans to Sentry
transaction.finish();

// All events that have not been sent yet are being flushed on JVM exit. Events can be also
// flushed manually:
// Sentry.close();
Expand Down
2 changes: 2 additions & 0 deletions sentry-samples/sentry-samples-spring-boot/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ repositories {
dependencies {
implementation(Config.Libs.springBootStarterSecurity)
implementation(Config.Libs.springBootStarterWeb)
implementation(Config.Libs.springBootStarterAop)
implementation(Config.Libs.aspectj)
implementation(Config.Libs.springBootStarter)
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,13 @@
@RestController
@RequestMapping("/person/")
public class PersonController {
private final PersonService personService;
private static final Logger LOGGER = LoggerFactory.getLogger(PersonController.class);

public PersonController(PersonService personService) {
this.personService = personService;
}

@GetMapping("{id}")
Person person(@PathVariable Long id) {
LOGGER.info("Loading person with id={}", id);
Expand All @@ -22,7 +27,6 @@ Person person(@PathVariable Long id) {

@PostMapping
Person create(@RequestBody Person person) {
LOGGER.warn("Creating person: {}", person);
return person;
return personService.create(person);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.sentry.samples.spring.boot;

import io.sentry.spring.tracing.SentrySpan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class PersonService {
private static final Logger LOGGER = LoggerFactory.getLogger(PersonService.class);

@SentrySpan
Person create(Person person) {
LOGGER.warn("Creating person: {}", person);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
return person;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,18 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class SentryDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SentryDemoApplication.class, args);
}

@Bean
RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.sentry.samples.spring.boot;

public class Todo {
private final Long id;
private final String title;
private final boolean completed;

public Todo(Long id, String title, boolean completed) {
this.id = id;
this.title = title;
this.completed = completed;
}

public Long getId() {
return id;
}

public String getTitle() {
return title;
}

public boolean isCompleted() {
return completed;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.sentry.samples.spring.boot;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class TodoController {
private final RestTemplate restTemplate;

public TodoController(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}

@GetMapping("/todo/{id}")
Todo todo(@PathVariable Long id) {
return restTemplate.getForObject(
"https://jsonplaceholder.typicode.com/todos/{id}", Todo.class, id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ sentry.max-breadcrumbs=150
# Logback integration configuration options
sentry.logging.minimum-event-level=info
sentry.logging.minimum-breadcrumb-level=debug
# Performance configuration
sentry.enable-tracing=true
sentry.traces-sample-rate=1.0
2 changes: 2 additions & 0 deletions sentry-spring-boot-starter/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ dependencies {
implementation(Config.Libs.springBootStarter)
compileOnly(Config.Libs.springWeb)
compileOnly(Config.Libs.servletApi)
compileOnly(Config.Libs.springBootStarterAop)

annotationProcessor(Config.AnnotationProcessors.springBootAutoConfigure)
annotationProcessor(Config.AnnotationProcessors.springBootConfiguration)
Expand All @@ -56,6 +57,7 @@ dependencies {
testImplementation(Config.Libs.springBootStarterTest)
testImplementation(Config.Libs.springBootStarterWeb)
testImplementation(Config.Libs.springBootStarterSecurity)
testImplementation(Config.Libs.springBootStarterAop)
testImplementation(Config.TestLibs.awaitility)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,41 @@
import io.sentry.SentryOptions;
import io.sentry.protocol.SdkVersion;
import io.sentry.spring.SentryExceptionResolver;
import io.sentry.spring.SentryRequestResolver;
import io.sentry.spring.SentryUserProvider;
import io.sentry.spring.SentryUserProviderEventProcessor;
import io.sentry.spring.SentryWebConfiguration;
import io.sentry.spring.tracing.SentrySpan;
import io.sentry.spring.tracing.SentrySpanAdvice;
import io.sentry.spring.tracing.SentryTracingFilter;
import io.sentry.spring.tracing.SentryTransaction;
import io.sentry.spring.tracing.SentryTransactionAdvice;
import io.sentry.transport.ITransport;
import io.sentry.transport.ITransportGate;
import java.util.List;
import org.aopalliance.aop.Advice;
import org.aspectj.lang.ProceedingJoinPoint;
import org.jetbrains.annotations.NotNull;
import org.springframework.aop.Advisor;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.info.GitProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.web.client.RestTemplate;

@Configuration
@ConditionalOnProperty(name = "sentry.dsn")
Expand Down Expand Up @@ -100,6 +119,90 @@ static class SentryWebMvcConfiguration {
final @NotNull IHub sentryHub, final @NotNull SentryProperties options) {
return new SentryExceptionResolver(sentryHub, options.getExceptionResolverOrder());
}

@Bean
@ConditionalOnProperty(name = "sentry.enable-tracing", havingValue = "true")
@ConditionalOnMissingBean(name = "sentryTracingFilter")
public FilterRegistrationBean<SentryTracingFilter> sentryTracingFilter(
final @NotNull IHub hub,
final @NotNull SentryOptions options,
final @NotNull SentryRequestResolver sentryRequestResolver) {
FilterRegistrationBean<SentryTracingFilter> filter =
new FilterRegistrationBean<>(
new SentryTracingFilter(hub, options, sentryRequestResolver));
filter.setOrder(Ordered.HIGHEST_PRECEDENCE);
return filter;
}
}

@Configuration
@ConditionalOnProperty(name = "sentry.enable-tracing", havingValue = "true")
@ConditionalOnClass(ProceedingJoinPoint.class)
@Open
static class SentryPerformanceAspectsConfiguration {

/**
* Pointcut around which transactions are created.
*
* <p>This bean is can be replaced with user defined pointcut by specifying a {@link Pointcut}
* bean with name "sentryTransactionPointcut".
*
* @return pointcut used by {@link SentryTransactionAdvice}.
*/
@Bean
@ConditionalOnMissingBean(name = "sentryTransactionPointcut")
public @NotNull Pointcut sentryTransactionPointcut() {
return new AnnotationMatchingPointcut(null, SentryTransaction.class);
}

@Bean
public @NotNull Advice sentryTransactionAdvice(final @NotNull IHub hub) {
return new SentryTransactionAdvice(hub);
}

@Bean
public @NotNull Advisor sentryTransactionAdvisor(
final @NotNull IHub hub,
final @NotNull @Qualifier("sentryTransactionPointcut") Pointcut
sentryTransactionPointcut) {
return new DefaultPointcutAdvisor(sentryTransactionPointcut, sentryTransactionAdvice(hub));
}

/**
* Pointcut around which spans are created.
*
* <p>This bean is can be replaced with user defined pointcut by specifying a {@link Pointcut}
* bean with name "sentrySpanPointcut".
*
* @return pointcut used by {@link SentrySpanAdvice}.
*/
@Bean
@ConditionalOnMissingBean(name = "sentrySpanPointcut")
public @NotNull Pointcut sentrySpanPointcut() {
return new AnnotationMatchingPointcut(null, SentrySpan.class);
}

@Bean
public @NotNull Advice sentrySpanAdvice(final @NotNull IHub hub) {
return new SentrySpanAdvice(hub);
}

@Bean
public @NotNull Advisor sentrySpanAdvisor(
IHub hub, final @NotNull @Qualifier("sentrySpanPointcut") Pointcut sentrySpanPointcut) {
return new DefaultPointcutAdvisor(sentrySpanPointcut, sentrySpanAdvice(hub));
}
}

@Configuration
@AutoConfigureBefore(RestTemplateAutoConfiguration.class)
@ConditionalOnClass(RestTemplate.class)
@Open
static class SentryPerformanceRestTemplateConfiguration {
@Bean
public SentrySpanRestTemplateCustomizer sentrySpanRestTemplateCustomizer(IHub hub) {
return new SentrySpanRestTemplateCustomizer(hub);
}
}

private static @NotNull SdkVersion createSdkVersion(
Expand Down
Loading

0 comments on commit 3c87a48

Please sign in to comment.