/*
* Copyright 2014-2015. Adaptive.me.
*
* 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
*
* http://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 me.adaptive.che.infrastructure.api;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import com.wordnik.swagger.annotations.*;
import me.adaptive.core.data.api.UserEntityService;
import me.adaptive.core.data.domain.BuildRequestEntity;
import me.adaptive.core.data.domain.MetricServerEntity;
import me.adaptive.core.data.domain.UserEntity;
import me.adaptive.core.data.repo.BuildRequestRepository;
import me.adaptive.core.data.repo.MetricServerRepository;
import me.adaptive.core.data.repo.UserRepository;
import org.apache.commons.collections.bidimap.TreeBidiMap;
import org.eclipse.che.api.core.ConflictException;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.rest.Service;
import org.eclipse.che.api.core.rest.annotations.GenerateLink;
import org.eclipse.che.inject.DynaModule;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import javax.ws.rs.*;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.*;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static javax.ws.rs.core.MediaType.TEXT_PLAIN;
/**
* Module for exposing the metrics for the dashbar calls.
* Created by ferranvila on 12/08/15.
*/
@DynaModule
@Api(value = "/metrics", description = "Metrics Module")
@Path("/metrics")
public class MetricsModule extends Service {
@Named("userEntityService")
@Inject
UserEntityService userEntityService;
@Named("metricServerRepository")
@Inject
MetricServerRepository metricServerRepository;
@Named("buildRequestRepository")
@Inject
BuildRequestRepository buildRequestRepository;
@Named("userRepository")
@Inject
UserRepository userRepository;
/**
* Returns the total number of user of the system
*
* @return String with the total number of users
* @throws ServerException Server Exception when some error is produced
*/
@ApiOperation(value = "Total number of users",
notes = "Returns the total number of users in the system",
response = String.class,
position = 1)
@ApiResponses({@ApiResponse(code = 200, message = "Successful operation"),
@ApiResponse(code = 500, message = "Internal Server Error")})
@GET
@Path("/user/total")
@GenerateLink(rel = "total users")
@Produces(TEXT_PLAIN)
public String totalUsers() throws ServerException, ConflictException {
try {
return String.valueOf(userEntityService.count());
} catch (Exception e) {
throw new ConflictException(e.getMessage());
}
}
// TODO: javadoc
// TODO: apiresponses and exceptions
/**
* Returns the number of user's builds per platform or the total number of builds
*
* @param platform Platform: ios, android or total
* @return Return the number of builds
* @throws NotFoundException When the platform is not found
*/
@ApiOperation(value = "Build metrics",
notes = "Returns the build metrics information requested",
response = Map.class,
position = 2)
@ApiResponses({@ApiResponse(code = 404, message = "Platform Not Found")})
@GET
@Path("/build/{metric}/{aggregation}/{startDate}/{endDate}")
@GenerateLink(rel = "build metrics")
@Produces(APPLICATION_JSON)
public Map<String, Double> buildMetrics(
@ApiParam(value = "platform", required = false)
@QueryParam("platform")
String platform,
@ApiParam(value = "user_id", required = false)
@QueryParam("user_id")
String user_id,
@ApiParam(value = "metric", required = true)
@PathParam("metric")
String metric,
@ApiParam(value = "aggregation", required = true)
@PathParam("aggregation")
String aggregation,
@ApiParam(value = "startDate", required = true)
@PathParam("startDate")
long startDate,
@ApiParam(value = "endDate", required = true)
@PathParam("endDate")
long endDate) throws NotFoundException {
Map<String, Double> map = new TreeMap<>();
Map<String, Double> dayOccurrences = new TreeMap<>();
LocalDate start = new Date(startDate).toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
LocalDate end = new Date(endDate).toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
// Fill the map with the values of the x-axis
switch (aggregation) {
case "sum":
map.put("sum", 0.0);
break;
case "day":
for (LocalDate date = start; date.isBefore(end); date = date.plusDays(1)) {
map.put(date.toString(), 0.0);
dayOccurrences.put(date.toString(), 0.0);
}
break;
default:
throw new NotFoundException("The aggregation {"+aggregation+"} is not found in the system. " +
"Should be: [sum,day]");
}
Set<BuildRequestEntity> values;
if (user_id != null && platform != null) {
Optional<UserEntity> user = userRepository.findByUserId(user_id);
values = buildRequestRepository.findByPlatformAndRequesterAndStartTimeBetween(platform, user.get(), new Date(startDate), new Date(endDate));
} else if (user_id == null && platform != null) {
values = buildRequestRepository.findByPlatformAndStartTimeBetween(platform, new Date(startDate), new Date(endDate));
} else if (user_id != null && platform == null) {
Optional<UserEntity> user = userRepository.findByUserId(user_id);
values = buildRequestRepository.findByRequesterAndStartTimeBetween(user.get(), new Date(startDate), new Date(endDate));
} else {
values = buildRequestRepository.findByStartTimeBetween(new Date(startDate), new Date(endDate));
}
// Depending on the metric
switch (metric) {
case "total":
switch (aggregation) {
case "sum":
map.put("sum", (double) values.size());
break;
case "day":
for (BuildRequestEntity event : values) {
String eventDate = event.getStartTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate().toString();
map.put(eventDate, map.get(eventDate) + 1);
}
break;
default:
throw new NotFoundException("The aggregation {"+aggregation+"} is not found in the system. " +
"Should be: [sum,day]");
}
break;
case "time":
switch (aggregation) {
case "sum":
double sum = 0;
for (BuildRequestEntity event : values) {
System.out.println(event.getEndTime().getTime() - event.getStartTime().getTime() + "ms");
sum += (event.getEndTime().getTime() - event.getStartTime().getTime());
}
if (values.size()>0){
map.put("sum", sum/values.size());
}
break;
case "day":
for (BuildRequestEntity event : values) {
String eventDate = event.getStartTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate().toString();
map.put(eventDate, map.get(eventDate) + (event.getEndTime().getTime() - event.getStartTime().getTime()));
dayOccurrences.put(eventDate, dayOccurrences.get(eventDate) + 1);
}
for (Map.Entry<String, Double> entry : map.entrySet()) {
if (dayOccurrences.get(entry.getKey()) > 0) {
map.put(entry.getKey(), (entry.getValue() / dayOccurrences.get(entry.getKey())));
}
}
break;
default:
throw new NotFoundException("The aggregation {"+aggregation+"} is not found in the system. " +
"Should be: [sum,day]");
}
break;
default:
throw new NotFoundException("The metric {"+metric+"} is not found in the system. Should be: [total,time]");
}
return map;
}
/**
* Returns the specified number of values for a specific metric on
* a specific server
*
* @param server Hosname of the server to query
* @param metric Metric to query
* @param number Number of values
* @return Metric's values
* @throws ServerException When some error is produced on the server
* @throws ConflictException When there are issues with the parameters
* @throws NotFoundException When the platform is not found
*/
@ApiOperation(value = "Server metric last values",
notes = "Returns the specified values for a metric in a concrete server",
response = Map.class,
position = 3)
@ApiResponses({@ApiResponse(code = 200, message = "Successful operation"),
@ApiResponse(code = 401, message = "Invalid parameters"),
@ApiResponse(code = 404, message = "Platform Not Found"),
@ApiResponse(code = 500, message = "Internal Server Error")})
@GET
@Path("/server/{server}/{metric}/{number}")
@GenerateLink(rel = "server metric values")
@Produces(APPLICATION_JSON)
public Map<String, String> serverMetricValues(
@ApiParam(value = "server", required = true)
@PathParam("server")
String server,
@ApiParam(value = "metric", required = true)
@PathParam("metric")
String metric,
@ApiParam(value = "number", required = false, defaultValue = "20")
@PathParam("number")
String number) throws ServerException, ConflictException, NotFoundException {
Map<String, String> map = new TreeMap<>();
Page<MetricServerEntity> values = metricServerRepository.findByHostnameAndAttributeKey(server, metric,
new PageRequest(0, Integer.valueOf(number), new Sort(Sort.Direction.DESC, "createdAt")));
for (MetricServerEntity ms : values){
map.put(ms.getCreatedAt().toString(), ms.getAttributes().get(metric));
}
return map;
}
}