diff --git a/src/main/java/de/tum/in/www1/artemis/config/MetricsBean.java b/src/main/java/de/tum/in/www1/artemis/config/MetricsBean.java
index 47e02b7a6233..31a23f52c6e6 100644
--- a/src/main/java/de/tum/in/www1/artemis/config/MetricsBean.java
+++ b/src/main/java/de/tum/in/www1/artemis/config/MetricsBean.java
@@ -41,10 +41,7 @@
import de.tum.in.www1.artemis.repository.StudentExamRepository;
import de.tum.in.www1.artemis.repository.UserRepository;
import de.tum.in.www1.artemis.security.SecurityUtils;
-import io.micrometer.core.instrument.Gauge;
-import io.micrometer.core.instrument.MeterRegistry;
-import io.micrometer.core.instrument.MultiGauge;
-import io.micrometer.core.instrument.Tags;
+import io.micrometer.core.instrument.*;
@Component
public class MetricsBean {
@@ -57,6 +54,8 @@ public class MetricsBean {
private static final String ARTEMIS_HEALTH_TAG = "healthindicator";
+ private static final String NO_SEMESTER_TAG = "No semester";
+
private static final int LOGGING_DELAY_SECONDS = 10;
/**
@@ -331,7 +330,7 @@ interface NowEndDateActiveUserNamesMetricsEntryFunction {
* - the current date
* - an end date (which is the current date + one of the values of MINUTE_RANGES_LOOKAHEAD), and
* - a list of active users (that is, users that created a submission within the last 14 days).
- *
+ *
* The implementing method may decide to ignore certain arguments.
* The method returns a list of ExerciseTypeMetricsEntries, that each correspond to an exercise type and a value.
*
@@ -350,7 +349,7 @@ interface NowEndDateActiveUserNamesIntegerFunction {
* This interface is used to calculate and expose metrics that are based on
* - the current date, and
* - an end date (which is the current date + one of the values of MINUTE_RANGES_LOOKAHEAD).
- *
+ *
* The implementing method may decide to ignore certain arguments.
* The method returns an integer, representing the corresponding value.
*
@@ -471,32 +470,57 @@ public void updatePublicArtemisMetrics() {
}
private void updateActiveUserMultiGauge(ZonedDateTime now) {
- var activeUserPeriodsInDays = new Integer[] { 1, 7, 14, 30 };
- activeUserMultiGauge.register(Stream.of(activeUserPeriodsInDays)
- .map(periodInDays -> MultiGauge.Row.of(Tags.of("period", periodInDays.toString()), statisticsRepository.countActiveUsers(now.minusDays(periodInDays), now)))
- // A mutable list is required here because otherwise the values can not be updated correctly
- .collect(Collectors.toCollection(ArrayList::new)), true);
+ final Integer[] activeUserPeriodsInDays = new Integer[] { 1, 7, 14, 30 };
+
+ // A mutable list is required here because otherwise the values can not be updated correctly
+ final List> gauges = Stream.of(activeUserPeriodsInDays).map(periodInDays -> {
+ final Tags tags = Tags.of("period", periodInDays.toString());
+ final long activeUsers = statisticsRepository.countActiveUsers(now.minusDays(periodInDays), now);
+ return MultiGauge.Row.of(tags, activeUsers);
+ }).collect(Collectors.toCollection(ArrayList::new));
+
+ activeUserMultiGauge.register(gauges, true);
}
private void updateStudentsCourseMultiGauge(List activeCourses) {
- studentsCourseGauge.register(
- activeCourses.stream().map(course -> MultiGauge.Row.of(Tags.of("courseName", course.getTitle(), "semester", course.getSemester()), course.getNumberOfStudents()))
- // A mutable list is required here because otherwise the values can not be updated correctly
- .collect(Collectors.toCollection(ArrayList::new)),
- true);
+ // A mutable list is required here because otherwise the values can not be updated correctly
+ final List> gauges = activeCourses.stream().map(course -> {
+ final Tags tags = Tags.of("courseId", Long.toString(course.getId()), "courseName", course.getTitle(), "semester", course.getSemester());
+ final long studentCount = course.getNumberOfStudents();
+ return MultiGauge.Row.of(tags, studentCount);
+ }).collect(Collectors.toCollection(ArrayList::new));
+
+ studentsCourseGauge.register(gauges, true);
}
private void updateStudentsExamMultiGauge(List examsInActiveCourses, List courses) {
- studentsExamGauge.register(examsInActiveCourses.stream()
- .map(exam -> MultiGauge.Row.of(Tags.of("examName", exam.getTitle(), "semester", getExamSemester(courses, exam)),
- studentExamRepository.findByExamId(exam.getId()).size()))
- // A mutable list is required here because otherwise the values can not be updated correctly
- .collect(Collectors.toCollection(ArrayList::new)), true);
+ // A mutable list is required here because otherwise the values can not be updated correctly
+ final List> gauges = examsInActiveCourses.stream().map(exam -> {
+ final Tags tags = getExamMetricTags(courses, exam);
+ final long studentCount = studentExamRepository.countByExamId(exam.getId());
+ return MultiGauge.Row.of(tags, studentCount);
+ }).collect(Collectors.toCollection(ArrayList::new));
+
+ studentsExamGauge.register(gauges, true);
+ }
+
+ private Tags getExamMetricTags(final List courses, final Exam exam) {
+ final Optional examCourse = findExamCourse(courses, exam);
+
+ final List tags = new ArrayList<>();
+ examCourse.ifPresent(course -> {
+ tags.add(Tag.of("courseId", Long.toString(course.getId())));
+ tags.add(Tag.of("courseName", course.getTitle()));
+ });
+ tags.add(Tag.of("examId", Long.toString(exam.getId())));
+ tags.add(Tag.of("examName", exam.getTitle()));
+ tags.add(Tag.of("semester", examCourse.map(Course::getSemester).orElse(NO_SEMESTER_TAG)));
+
+ return Tags.of(tags);
}
- private String getExamSemester(final List courses, final Exam exam) {
- // The exam.getCourse() is not populated (the semester property is not set) -> Use course from the courses list, which contains the semester
- return courses.stream().filter(course -> Objects.equals(course.getId(), exam.getCourse().getId())).findAny().map(Course::getSemester).orElse("No semester");
+ private Optional findExamCourse(final List courses, final Exam exam) {
+ return courses.stream().filter(course -> Objects.equals(course.getId(), exam.getCourse().getId())).findAny();
}
private void updateActiveExerciseMultiGauge() {
@@ -530,7 +554,7 @@ private void extractExerciseTypeMetricsAndAddToMetricsResults(List courses) {
courses.forEach(course -> {
if (course.getSemester() == null) {
- course.setSemester("No semester");
+ course.setSemester(NO_SEMESTER_TAG);
}
if (course.getTitle() == null) {
if (course.getShortName() != null) {
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/StudentExamRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/StudentExamRepository.java
index b880005a94d2..8f91a855d803 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/StudentExamRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/StudentExamRepository.java
@@ -71,10 +71,18 @@ public interface StudentExamRepository extends JpaRepository
SELECT se
FROM StudentExam se
WHERE se.exam.id = :examId
- AND se.testRun = FALSE
+ AND se.testRun = FALSE
""")
Set findByExamId(@Param("examId") long examId);
+ @Query("""
+ SELECT COUNT(DISTINCT se)
+ FROM StudentExam se
+ WHERE se.exam.id = :examId
+ AND se.testRun = FALSE
+ """)
+ long countByExamId(@Param("examId") long examId);
+
@Query("""
SELECT se
FROM StudentExam se
diff --git a/src/test/java/de/tum/in/www1/artemis/config/MetricsBeanTest.java b/src/test/java/de/tum/in/www1/artemis/config/MetricsBeanTest.java
index 452203bea36c..f4074f1ad64b 100644
--- a/src/test/java/de/tum/in/www1/artemis/config/MetricsBeanTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/config/MetricsBeanTest.java
@@ -225,11 +225,11 @@ void testPublicMetricsExams() {
metricsBean.updatePublicArtemisMetrics();
- var totalNumberOfExams = examRepository.count();
- var numberOfActiveExams = examRepository.countAllActiveExams(ZonedDateTime.now());
+ long totalNumberOfExams = examRepository.count();
+ long numberOfActiveExams = examRepository.countAllActiveExams(ZonedDateTime.now());
// Assert that there is at least one non-active exam in the database so that the values returned from the metrics are different
- assertThat(numberOfActiveExams.intValue()).isNotEqualTo(totalNumberOfExams);
+ assertThat(numberOfActiveExams).isNotEqualTo(totalNumberOfExams);
assertMetricEquals(totalNumberOfExams, "artemis.statistics.public.exams");
assertMetricEquals(numberOfActiveExams, "artemis.statistics.public.active_exams");
@@ -240,9 +240,10 @@ void testPublicMetricsCourseAndExamStudents() {
var users = userUtilService.addUsers(TEST_PREFIX, 3, 0, 0, 0);
var course1 = courseUtilService.createCourse();
+ course1.setTitle("Course 1");
course1.setSemester(null);
course1.setStudentGroupName(TEST_PREFIX + "course1Students");
- courseRepository.save(course1);
+ course1 = courseRepository.save(course1);
var exam1 = examUtilService.addExamWithModellingAndTextAndFileUploadAndQuizAndEmptyGroup(course1);
exam1.setStartDate(ZonedDateTime.now().minusMinutes(1));
exam1.setTitle("exam" + UUID.randomUUID());
@@ -253,7 +254,7 @@ void testPublicMetricsCourseAndExamStudents() {
var course2 = courseUtilService.createCourse();
course2.setSemester("WS 2023/24");
course2.setStudentGroupName(TEST_PREFIX + "course2Students");
- courseRepository.save(course2);
+ course2 = courseRepository.save(course2);
var exam2 = examUtilService.addExamWithModellingAndTextAndFileUploadAndQuizAndEmptyGroup(course2);
exam2.setTitle("exam" + UUID.randomUUID());
exam2.setStartDate(ZonedDateTime.now().minusMinutes(1));
@@ -269,11 +270,16 @@ void testPublicMetricsCourseAndExamStudents() {
metricsBean.updatePublicArtemisMetrics();
- assertMetricEquals(3, "artemis.statistics.public.course_students", "courseName", course1.getTitle(), "semester", "No semester");
- assertMetricEquals(1, "artemis.statistics.public.course_students", "courseName", course2.getTitle(), "semester", "WS 2023/24");
+ String course1Id = Long.toString(course1.getId());
+ String course2Id = Long.toString(course2.getId());
+
+ assertMetricEquals(3, "artemis.statistics.public.course_students", "courseId", course1Id, "courseName", course1.getTitle(), "semester", "No semester");
+ assertMetricEquals(1, "artemis.statistics.public.course_students", "courseId", course2Id, "courseName", course2.getTitle(), "semester", "WS 2023/24");
- assertMetricEquals(2, "artemis.statistics.public.exam_students", "examName", exam1.getTitle(), "semester", "No semester");
- assertMetricEquals(1, "artemis.statistics.public.exam_students", "examName", exam2.getTitle(), "semester", "WS 2023/24");
+ assertMetricEquals(2, "artemis.statistics.public.exam_students", "courseId", course1Id, "courseName", course1.getTitle(), "examId", Long.toString(exam1.getId()),
+ "examName", exam1.getTitle(), "semester", "No semester");
+ assertMetricEquals(1, "artemis.statistics.public.exam_students", "courseId", course2Id, "examId", Long.toString(exam2.getId()), "examName", exam2.getTitle(), "semester",
+ "WS 2023/24");
}
@Test