diff --git a/deploy/openshift/templates/monitoring/grafana-dashboards.yaml b/deploy/openshift/templates/monitoring/grafana-dashboards.yaml index ebda2bfd6dd..f8c012850b6 100644 --- a/deploy/openshift/templates/monitoring/grafana-dashboards.yaml +++ b/deploy/openshift/templates/monitoring/grafana-dashboards.yaml @@ -4287,6 +4287,95 @@ data: "x": 6, "y": 12 }, + "id": 49, + "legend": { + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null as zero", + "options": {}, + "paceLength": 10, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(che_workspace_stop_time_seconds_sum[5m])/rate(che_workspace_stop_time_seconds_count [5m])", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "{{result}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Average workspace stop time", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "$datasource", + "fill": 1, + "gridPos": { + "h": 5, + "w": 6, + "x": 12, + "y": 12 + }, "id": 8, "legend": { "avg": false, diff --git a/wsmaster/che-core-api-metrics/src/main/java/org/eclipse/che/api/metrics/WorkspaceStopTrackerMeterBinder.java b/wsmaster/che-core-api-metrics/src/main/java/org/eclipse/che/api/metrics/WorkspaceStopTrackerMeterBinder.java new file mode 100644 index 00000000000..cf51c2c1464 --- /dev/null +++ b/wsmaster/che-core-api-metrics/src/main/java/org/eclipse/che/api/metrics/WorkspaceStopTrackerMeterBinder.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.metrics; + +import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.*; +import static org.eclipse.che.api.metrics.WorkspaceBinders.withStandardTags; +import static org.eclipse.che.api.metrics.WorkspaceBinders.workspaceMetric; + +import com.google.common.annotations.VisibleForTesting; +import com.google.inject.Inject; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Timer; +import io.micrometer.core.instrument.binder.MeterBinder; +import java.time.Duration; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import javax.inject.Singleton; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link MeterBinder} that is providing metrics about workspace stop time. + * + *

We're measuring these state transitions: + * + *

+ *   RUNNING -> STOPPING -> STOPPED
+ *   STARTING -> STOPPING -> STOPPED
+ * 
+ */ +@Singleton +public class WorkspaceStopTrackerMeterBinder implements MeterBinder { + + private static final Logger LOG = LoggerFactory.getLogger(WorkspaceStopTrackerMeterBinder.class); + + private final EventService eventService; + + private final Map workspaceStopTime; + private Timer stopTimer; + + @Inject + public WorkspaceStopTrackerMeterBinder(EventService eventService) { + this(eventService, new ConcurrentHashMap<>()); + } + + @VisibleForTesting + WorkspaceStopTrackerMeterBinder(EventService eventService, Map workspaceStopTime) { + this.eventService = eventService; + this.workspaceStopTime = workspaceStopTime; + } + + @Override + public void bindTo(MeterRegistry registry) { + + stopTimer = + Timer.builder(workspaceMetric("stop.time")) + .description("The time of workspace stop") + .tags(withStandardTags("result", "success")) + .register(registry); + + // only subscribe to the event once we have the counters ready + eventService.subscribe(this::handleWorkspaceStatusChange, WorkspaceStatusEvent.class); + } + + private void handleWorkspaceStatusChange(WorkspaceStatusEvent event) { + if ((event.getPrevStatus() == RUNNING || event.getPrevStatus() == STARTING) + && event.getStatus() == STOPPING) { + workspaceStopTime.put(event.getWorkspaceId(), System.currentTimeMillis()); + } else if (event.getPrevStatus() == STOPPING) { + Long stopTime = workspaceStopTime.remove(event.getWorkspaceId()); + if (stopTime == null) { + LOG.warn("No workspace stop time recorded for workspace {}", event.getWorkspaceId()); + return; + } + + if (event.getStatus() == STOPPED) { + stopTimer.record(Duration.ofMillis(System.currentTimeMillis() - stopTime)); + } else { + LOG.error("Unexpected change of status from STOPPING to {}", event.getStatus()); + return; + } + } + } +} diff --git a/wsmaster/che-core-api-metrics/src/main/java/org/eclipse/che/api/metrics/WsMasterMetricsModule.java b/wsmaster/che-core-api-metrics/src/main/java/org/eclipse/che/api/metrics/WsMasterMetricsModule.java index ffd69142410..81244a490eb 100644 --- a/wsmaster/che-core-api-metrics/src/main/java/org/eclipse/che/api/metrics/WsMasterMetricsModule.java +++ b/wsmaster/che-core-api-metrics/src/main/java/org/eclipse/che/api/metrics/WsMasterMetricsModule.java @@ -29,6 +29,7 @@ protected void configure() { meterMultibinder.addBinding().to(WorkspaceActivityMeterBinder.class); meterMultibinder.addBinding().to(WorkspaceFailureMeterBinder.class); meterMultibinder.addBinding().to(WorkspaceStartTrackerMeterBinder.class); + meterMultibinder.addBinding().to(WorkspaceStopTrackerMeterBinder.class); meterMultibinder.addBinding().to(WorkspaceSuccessfulStartAttemptsMeterBinder.class); meterMultibinder.addBinding().to(WorkspaceSuccessfulStopAttemptsMeterBinder.class); meterMultibinder.addBinding().to(WorkspaceStartAttemptsMeterBinder.class); diff --git a/wsmaster/che-core-api-metrics/src/test/java/org/eclipse/che/api/metrics/WorkspaceStartTrackerMeterBinderTest.java b/wsmaster/che-core-api-metrics/src/test/java/org/eclipse/che/api/metrics/WorkspaceStartTrackerMeterBinderTest.java index 7f4e00cb625..8ca6c52fac7 100644 --- a/wsmaster/che-core-api-metrics/src/test/java/org/eclipse/che/api/metrics/WorkspaceStartTrackerMeterBinderTest.java +++ b/wsmaster/che-core-api-metrics/src/test/java/org/eclipse/che/api/metrics/WorkspaceStartTrackerMeterBinderTest.java @@ -19,7 +19,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; @@ -33,7 +32,6 @@ public class WorkspaceStartTrackerMeterBinderTest { - private Random rnd = new Random(); private EventService eventService; private MeterRegistry registry; private WorkspaceStartTrackerMeterBinder meterBinder; diff --git a/wsmaster/che-core-api-metrics/src/test/java/org/eclipse/che/api/metrics/WorkspaceStopTrackerMeterBinderTest.java b/wsmaster/che-core-api-metrics/src/test/java/org/eclipse/che/api/metrics/WorkspaceStopTrackerMeterBinderTest.java new file mode 100644 index 00000000000..448789b62ff --- /dev/null +++ b/wsmaster/che-core-api-metrics/src/test/java/org/eclipse/che/api/metrics/WorkspaceStopTrackerMeterBinderTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.metrics; + +import static java.util.Arrays.asList; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Timer; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import org.eclipse.che.api.core.model.workspace.WorkspaceStatus; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent; +import org.eclipse.che.dto.server.DtoFactory; +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +public class WorkspaceStopTrackerMeterBinderTest { + + private EventService eventService; + private MeterRegistry registry; + private WorkspaceStopTrackerMeterBinder meterBinder; + private Map workspaceStopTime; + + @BeforeMethod + public void setUp() { + eventService = new EventService(); + registry = new SimpleMeterRegistry(); + workspaceStopTime = new ConcurrentHashMap<>(); + meterBinder = new WorkspaceStopTrackerMeterBinder(eventService, workspaceStopTime); + meterBinder.bindTo(registry); + } + + @Test + public void shouldRecordWorkspaceStopTime() { + // given + // when + eventService.publish( + DtoFactory.newDto(WorkspaceStatusEvent.class) + .withPrevStatus(WorkspaceStatus.RUNNING) + .withStatus(WorkspaceStatus.STOPPING) + .withWorkspaceId("id1")); + // then + Assert.assertTrue(workspaceStopTime.containsKey("id1")); + } + + @Test(dataProvider = "allStatusTransitionsWithoutStarting") + public void shouldNotRecordWorkspaceStopTimeForNonStoppingStatuses( + WorkspaceStatus from, WorkspaceStatus to) { + // given + // when + eventService.publish( + DtoFactory.newDto(WorkspaceStatusEvent.class) + .withPrevStatus(from) + .withStatus(to) + .withWorkspaceId("id1")); + // then + Assert.assertTrue(workspaceStopTime.isEmpty()); + } + + @Test + public void shouldCountSuccessfulStart() { + // given + workspaceStopTime.put("id1", System.currentTimeMillis() - 60 * 1000); + // when + eventService.publish( + DtoFactory.newDto(WorkspaceStatusEvent.class) + .withPrevStatus(WorkspaceStatus.STOPPING) + .withStatus(WorkspaceStatus.STOPPED) + .withWorkspaceId("id1")); + + // then + + Timer t = registry.find("che.workspace.stop.time").tag("result", "success").timer(); + Assert.assertEquals(t.count(), 1); + Assert.assertTrue(t.totalTime(TimeUnit.MILLISECONDS) >= 60 * 1000); + } + + @DataProvider + public Object[][] allStatusTransitionsWithoutStarting() { + List> transitions = new ArrayList<>(9); + + for (WorkspaceStatus from : WorkspaceStatus.values()) { + for (WorkspaceStatus to : WorkspaceStatus.values()) { + if ((from == WorkspaceStatus.RUNNING || from == WorkspaceStatus.STARTING) + && to == WorkspaceStatus.STOPPING) { + continue; + } + + transitions.add(asList(from, to)); + } + } + + return transitions.stream().map(List::toArray).toArray(Object[][]::new); + } +}