/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.brooklyn.rest.resources; import static org.apache.brooklyn.rest.util.WebResourceUtils.notFound; import java.net.URI; import java.util.Date; import java.util.List; import java.util.Set; import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.brooklyn.core.entity.lifecycle.Lifecycle; import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; import org.apache.brooklyn.core.mgmt.usage.ApplicationUsage; import org.apache.brooklyn.core.mgmt.usage.LocationUsage; import org.apache.brooklyn.core.mgmt.usage.ApplicationUsage.ApplicationEvent; import org.apache.brooklyn.rest.api.UsageApi; import org.apache.brooklyn.rest.domain.UsageStatistic; import org.apache.brooklyn.rest.domain.UsageStatistics; import org.apache.brooklyn.rest.transform.ApplicationTransformer; import org.apache.brooklyn.util.exceptions.UserFacingException; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.time.Time; import com.google.common.base.Objects; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; public class UsageResource extends AbstractBrooklynRestResource implements UsageApi { private static final Logger log = LoggerFactory.getLogger(UsageResource.class); private static final Set<Lifecycle> WORKING_LIFECYCLES = ImmutableSet.of(Lifecycle.RUNNING, Lifecycle.CREATED, Lifecycle.STARTING); @Override public List<UsageStatistics> listApplicationsUsage(@Nullable String start, @Nullable String end) { log.debug("REST call to get application usage for all applications: dates {} -> {}", new Object[] {start, end}); List<UsageStatistics> response = Lists.newArrayList(); Date startDate = parseDate(start, new Date(0)); Date endDate = parseDate(end, new Date()); checkDates(startDate, endDate); Set<ApplicationUsage> usages = ((ManagementContextInternal) mgmt()).getUsageManager().getApplicationUsage(Predicates.alwaysTrue()); for (ApplicationUsage usage : usages) { List<UsageStatistic> statistics = retrieveApplicationUsage(usage, startDate, endDate); if (statistics.size() > 0) { response.add(new UsageStatistics(statistics, ImmutableMap.<String,URI>of())); } } return response; } @Override public UsageStatistics getApplicationUsage(String application, String start, String end) { log.debug("REST call to get application usage for application {}: dates {} -> {}", new Object[] {application, start, end}); Date startDate = parseDate(start, new Date(0)); Date endDate = parseDate(end, new Date()); checkDates(startDate, endDate); ApplicationUsage usage = ((ManagementContextInternal) mgmt()).getUsageManager().getApplicationUsage(application); if (usage != null) { List<UsageStatistic> statistics = retrieveApplicationUsage(usage, startDate, endDate); return new UsageStatistics(statistics, ImmutableMap.<String,URI>of()); } else { throw notFound("Application '%s' not found", application); } } private List<UsageStatistic> retrieveApplicationUsage(ApplicationUsage usage, Date startDate, Date endDate) { log.debug("Determining application usage for application {}: dates {} -> {}", new Object[] {usage.getApplicationId(), startDate, endDate}); log.trace("Considering application usage events of {}: {}", usage.getApplicationId(), usage.getEvents()); List<UsageStatistic> result = Lists.newArrayList(); // Getting duration of state by comparing with next event (if next event is of same type, we just generate two statistics)... for (int i = 0; i < usage.getEvents().size(); i++) { ApplicationEvent current = usage.getEvents().get(i); Date eventStartDate = current.getDate(); Date eventEndDate; if (i < usage.getEvents().size() - 1) { ApplicationEvent next = usage.getEvents().get(i + 1); eventEndDate = next.getDate(); } else if (current.getState() == Lifecycle.DESTROYED) { eventEndDate = eventStartDate; } else { eventEndDate = new Date(); } if (eventStartDate.compareTo(endDate) > 0 || eventEndDate.compareTo(startDate) < 0) { continue; } if (eventStartDate.compareTo(startDate) < 0) { eventStartDate = startDate; } if (eventEndDate.compareTo(endDate) > 0) { eventEndDate = endDate; } long duration = eventEndDate.getTime() - eventStartDate.getTime(); UsageStatistic statistic = new UsageStatistic(ApplicationTransformer.statusFromLifecycle(current.getState()), usage.getApplicationId(), usage.getApplicationId(), format(eventStartDate), format(eventEndDate), duration, usage.getMetadata()); log.trace("Adding application usage statistic to response for app {}: {}", usage.getApplicationId(), statistic); result.add(statistic); } return result; } @Override public List<UsageStatistics> listMachinesUsage(final String application, final String start, final String end) { log.debug("REST call to get machine usage for application {}: dates {} -> {}", new Object[] {application, start, end}); final Date startDate = parseDate(start, new Date(0)); final Date endDate = parseDate(end, new Date()); checkDates(startDate, endDate); // Note currently recording ALL metrics for a machine that contains an Event from given Application Set<LocationUsage> matches = ((ManagementContextInternal) mgmt()).getUsageManager().getLocationUsage(new Predicate<LocationUsage>() { @Override public boolean apply(LocationUsage input) { LocationUsage.LocationEvent first = input.getEvents().get(0); if (endDate.compareTo(first.getDate()) < 0) { return false; } LocationUsage.LocationEvent last = input.getEvents().get(input.getEvents().size() - 1); if (!WORKING_LIFECYCLES.contains(last.getState()) && startDate.compareTo(last.getDate()) > 0) { return false; } if (application != null) { for (LocationUsage.LocationEvent e : input.getEvents()) { if (Objects.equal(application, e.getApplicationId())) { return true; } } return false; } return true; } }); List<UsageStatistics> response = Lists.newArrayList(); for (LocationUsage usage : matches) { List<UsageStatistic> statistics = retrieveMachineUsage(usage, startDate, endDate); if (statistics.size() > 0) { response.add(new UsageStatistics(statistics, ImmutableMap.<String,URI>of())); } } return response; } @Override public UsageStatistics getMachineUsage(final String machine, final String start, final String end) { log.debug("REST call to get machine usage for machine {}: dates {} -> {}", new Object[] {machine, start, end}); final Date startDate = parseDate(start, new Date(0)); final Date endDate = parseDate(end, new Date()); checkDates(startDate, endDate); // Note currently recording ALL metrics for a machine that contains an Event from given Application LocationUsage usage = ((ManagementContextInternal) mgmt()).getUsageManager().getLocationUsage(machine); if (usage == null) { throw notFound("Machine '%s' not found", machine); } List<UsageStatistic> statistics = retrieveMachineUsage(usage, startDate, endDate); return new UsageStatistics(statistics, ImmutableMap.<String,URI>of()); } private List<UsageStatistic> retrieveMachineUsage(LocationUsage usage, Date startDate, Date endDate) { log.debug("Determining machine usage for location {}", usage.getLocationId()); log.trace("Considering machine usage events of {}: {}", usage.getLocationId(), usage.getEvents()); List<UsageStatistic> result = Lists.newArrayList(); // Getting duration of state by comparing with next event (if next event is of same type, we just generate two statistics)... for (int i = 0; i < usage.getEvents().size(); i++) { LocationUsage.LocationEvent current = usage.getEvents().get(i); Date eventStartDate = current.getDate(); Date eventEndDate; if (i < usage.getEvents().size() - 1) { LocationUsage.LocationEvent next = usage.getEvents().get(i + 1); eventEndDate = next.getDate(); } else if (current.getState() == Lifecycle.DESTROYED || current.getState() == Lifecycle.STOPPED) { eventEndDate = eventStartDate; } else { eventEndDate = new Date(); } if (eventStartDate.compareTo(endDate) > 0 || eventEndDate.compareTo(startDate) < 0) { continue; } if (eventStartDate.compareTo(startDate) < 0) { eventStartDate = startDate; } if (eventEndDate.compareTo(endDate) > 0) { eventEndDate = endDate; } long duration = eventEndDate.getTime() - eventStartDate.getTime(); UsageStatistic statistic = new UsageStatistic(ApplicationTransformer.statusFromLifecycle(current.getState()), usage.getLocationId(), current.getApplicationId(), format(eventStartDate), format(eventEndDate), duration, usage.getMetadata()); log.trace("Adding machine usage statistic to response for app {}: {}", usage.getLocationId(), statistic); result.add(statistic); } return result; } private void checkDates(Date startDate, Date endDate) { if (startDate.compareTo(endDate) > 0) { throw new UserFacingException(new IllegalArgumentException("Start must be less than or equal to end: " + startDate + " > " + endDate + " (" + startDate.getTime() + " > " + endDate.getTime() + ")")); } } private Date parseDate(String toParse, Date def) { return Strings.isBlank(toParse) ? def : Time.parseDate(toParse); } private String format(Date date) { return Time.makeDateString(date, Time.DATE_FORMAT_ISO8601_NO_MILLIS, Time.TIME_ZONE_UTC); } }