001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2013-2015 ForgeRock AS
025 */
026package org.opends.server.protocols.http;
027
028import java.util.Arrays;
029import java.util.HashMap;
030import java.util.List;
031import java.util.Map;
032import java.util.Map.Entry;
033import java.util.concurrent.atomic.AtomicInteger;
034import java.util.concurrent.atomic.AtomicLong;
035
036import org.opends.server.protocols.ldap.LDAPStatistics;
037import org.opends.server.types.Attribute;
038
039/**
040 * Collects statistics for HTTP. This class inherits from {@link LDAPStatistics}
041 * to show the administrator how the underlying LDAP internal operations are
042 * performing.
043 */
044public class HTTPStatistics extends LDAPStatistics
045{
046
047  /**
048   * Map containing the total number of requests per HTTP methods.
049   * <p>
050   * key: HTTP method => value: number of requests for that method.
051   * </p>
052   * Not using a ConcurrentMap implementation here because the keys are static.
053   * The keys are static because they need to be listed in the schema which is
054   * static.
055   */
056  private Map<String, AtomicInteger> requestMethodsTotalCount = new HashMap<>();
057  /**
058   * Map containing the total execution time for the requests per HTTP methods.
059   * <p>
060   * key: HTTP method => value: total execution time for requests using that
061   * method.
062   * </p>
063   * Not using a ConcurrentMap implementation here because the keys are static.
064   * The keys are static because they need to be listed in the schema which is
065   * static.
066   */
067  private Map<String, AtomicLong> requestMethodsTotalTime = new HashMap<>();
068  /**
069   * Total number of requests. The total number may be different than the sum of
070   * the supported HTTP methods above because clients could use unsupported HTTP
071   * methods.
072   */
073  private AtomicInteger requestsTotalCount = new AtomicInteger(0);
074
075  /**
076   * Constructor for this class.
077   *
078   * @param instanceName
079   *          The name for this monitor provider instance.
080   */
081  public HTTPStatistics(String instanceName)
082  {
083    super(instanceName);
084
085    // List the HTTP methods supported by Rest2LDAP
086    final List<String> supportedHttpMethods =
087        Arrays.asList("delete", "get", "patch", "post", "put");
088    for (String method : supportedHttpMethods)
089    {
090      requestMethodsTotalCount.put(method, new AtomicInteger(0));
091      requestMethodsTotalTime.put(method, new AtomicLong(0));
092    }
093  }
094
095  /** {@inheritDoc} */
096  @Override
097  public void clearStatistics()
098  {
099    this.requestMethodsTotalCount.clear();
100    this.requestMethodsTotalTime.clear();
101    this.requestsTotalCount.set(0);
102
103    super.clearStatistics();
104  }
105
106  /** {@inheritDoc} */
107  @Override
108  public List<Attribute> getMonitorData()
109  {
110    // first take a snapshot of all the data as fast as possible
111    final int totalCount = this.requestsTotalCount.get();
112    final Map<String, Integer> totalCountsSnapshot = new HashMap<>();
113    for (Entry<String, AtomicInteger> entry : requestMethodsTotalCount.entrySet())
114    {
115      totalCountsSnapshot.put(entry.getKey(), entry.getValue().get());
116    }
117    final Map<String, Long> totalTimesSnapshot = new HashMap<>();
118    for (Entry<String, AtomicLong> entry1 : requestMethodsTotalTime.entrySet())
119    {
120      totalTimesSnapshot.put(entry1.getKey(), entry1.getValue().get());
121    }
122
123    // do the same with the underlying data
124    final List<Attribute> results = super.getMonitorData();
125    addAll(results, totalCountsSnapshot, "ds-mon-http-", "-requests-total-count");
126    addAll(results, totalTimesSnapshot, "ds-mon-resident-time-http-", "-requests-total-time");
127    results.add(createAttribute("ds-mon-http-requests-total-count", Integer.toString(totalCount)));
128    return results;
129  }
130
131  private void addAll(final List<Attribute> results,
132      final Map<String, ?> toOutput, String prefix, String suffix)
133  {
134    for (Entry<String, ?> entry : toOutput.entrySet())
135    {
136      final String httpMethod = entry.getKey();
137      final String nb = entry.getValue().toString();
138      results.add(createAttribute(prefix + httpMethod + suffix, nb));
139    }
140  }
141
142  /**
143   * Adds a request to the stats using the provided HTTP method.
144   *
145   * @param httpMethod
146   *          the method of the HTTP request to add to the stats
147   * @throws NullPointerException
148   *           if the httpMethod is null
149   */
150  public void addRequest(String httpMethod) throws NullPointerException
151  {
152    AtomicInteger nb =
153        this.requestMethodsTotalCount.get(httpMethod.toLowerCase());
154    if (nb != null)
155    {
156      nb.incrementAndGet();
157    } // else this is an unsupported HTTP method
158    // always count any requests regardless of whether the method is supported
159    this.requestsTotalCount.incrementAndGet();
160  }
161
162  /**
163   * Adds to the total time of an HTTP request method.
164   *
165   * @param httpMethod
166   *          the method of the HTTP request to add to the stats
167   * @param time
168   *          the time to add to the total
169   * @throws NullPointerException
170   *           if the httpMethod is null
171   */
172  public void updateRequestMonitoringData(String httpMethod, long time)
173      throws NullPointerException
174  {
175    AtomicLong nb = this.requestMethodsTotalTime.get(httpMethod.toLowerCase());
176    if (nb != null)
177    {
178      nb.addAndGet(time);
179    } // else this is an unsupported HTTP method
180  }
181}