Add Histogram0..3 to track bytes sent

The pack_bytes metric reports on the size of data sent to clients.
Using a histogram allows the server to track the distribution of these
files over time, as well as total amount sent.

The response_bytes metric reports on size of REST API replies,
broken down by view.

Change-Id: I7c06cb9afb2f4a8da1ef1eec85b5831f75b88116
This commit is contained in:
Shawn Pearce
2015-11-12 17:34:26 -08:00
parent 3ac37ad526
commit a093f127d4
15 changed files with 594 additions and 32 deletions

View File

@@ -20,6 +20,7 @@ import com.google.gerrit.metrics.Counter1;
import com.google.gerrit.metrics.Counter2; import com.google.gerrit.metrics.Counter2;
import com.google.gerrit.metrics.Description; import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field; import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.Histogram1;
import com.google.gerrit.metrics.MetricMaker; import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer1; import com.google.gerrit.metrics.Timer1;
import com.google.gerrit.metrics.Description.Units; import com.google.gerrit.metrics.Description.Units;
@@ -36,6 +37,7 @@ public class RestApiMetrics {
final Counter1<String> count; final Counter1<String> count;
final Counter2<String, Integer> errorCount; final Counter2<String, Integer> errorCount;
final Timer1<String> serverLatency; final Timer1<String> serverLatency;
final Histogram1<String> responseBytes;
@Inject @Inject
RestApiMetrics(MetricMaker metrics) { RestApiMetrics(MetricMaker metrics) {
@@ -59,6 +61,13 @@ public class RestApiMetrics {
.setCumulative() .setCumulative()
.setUnit(Units.MILLISECONDS), .setUnit(Units.MILLISECONDS),
view); view);
responseBytes = metrics.newHistogram(
"http/server/rest_api/response_bytes",
new Description("Size of response on network (may be gzip compressed)")
.setCumulative()
.setUnit(Units.BYTES),
view);
} }
String view(ViewData viewData) { String view(ViewData viewData) {

View File

@@ -43,6 +43,7 @@ import com.google.common.collect.Maps;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.common.io.BaseEncoding; import com.google.common.io.BaseEncoding;
import com.google.common.io.CountingOutputStream;
import com.google.common.math.IntMath; import com.google.common.math.IntMath;
import com.google.common.net.HttpHeaders; import com.google.common.net.HttpHeaders;
import com.google.gerrit.audit.AuditService; import com.google.gerrit.audit.AuditService;
@@ -206,6 +207,7 @@ public class RestApiServlet extends HttpServlet {
res.setHeader("Content-Disposition", "attachment"); res.setHeader("Content-Disposition", "attachment");
res.setHeader("X-Content-Type-Options", "nosniff"); res.setHeader("X-Content-Type-Options", "nosniff");
int status = SC_OK; int status = SC_OK;
long responseBytes = -1;
Object result = null; Object result = null;
Multimap<String, String> params = LinkedHashMultimap.create(); Multimap<String, String> params = LinkedHashMultimap.create();
Object inputRequestBody = null; Object inputRequestBody = null;
@@ -353,46 +355,47 @@ public class RestApiServlet extends HttpServlet {
if (result != Response.none()) { if (result != Response.none()) {
result = Response.unwrap(result); result = Response.unwrap(result);
if (result instanceof BinaryResult) { if (result instanceof BinaryResult) {
replyBinaryResult(req, res, (BinaryResult) result); responseBytes = replyBinaryResult(req, res, (BinaryResult) result);
} else { } else {
replyJson(req, res, config, result); responseBytes = replyJson(req, res, config, result);
} }
} }
} catch (MalformedJsonException e) { } catch (MalformedJsonException e) {
replyError(req, res, status = SC_BAD_REQUEST, responseBytes = replyError(req, res, status = SC_BAD_REQUEST,
"Invalid " + JSON_TYPE + " in request", e); "Invalid " + JSON_TYPE + " in request", e);
} catch (JsonParseException e) { } catch (JsonParseException e) {
replyError(req, res, status = SC_BAD_REQUEST, responseBytes = replyError(req, res, status = SC_BAD_REQUEST,
"Invalid " + JSON_TYPE + " in request", e); "Invalid " + JSON_TYPE + " in request", e);
} catch (BadRequestException e) { } catch (BadRequestException e) {
replyError(req, res, status = SC_BAD_REQUEST, messageOr(e, "Bad Request"), responseBytes = replyError(req, res, status = SC_BAD_REQUEST,
e.caching(), e); messageOr(e, "Bad Request"), e.caching(), e);
} catch (AuthException e) { } catch (AuthException e) {
replyError(req, res, status = SC_FORBIDDEN, messageOr(e, "Forbidden"), responseBytes = replyError(req, res, status = SC_FORBIDDEN,
e.caching(), e); messageOr(e, "Forbidden"), e.caching(), e);
} catch (AmbiguousViewException e) { } catch (AmbiguousViewException e) {
replyError(req, res, status = SC_NOT_FOUND, messageOr(e, "Ambiguous"), e); responseBytes = replyError(req, res, status = SC_NOT_FOUND,
messageOr(e, "Ambiguous"), e);
} catch (ResourceNotFoundException e) { } catch (ResourceNotFoundException e) {
replyError(req, res, status = SC_NOT_FOUND, messageOr(e, "Not Found"), responseBytes = replyError(req, res, status = SC_NOT_FOUND,
e.caching(), e); messageOr(e, "Not Found"), e.caching(), e);
} catch (MethodNotAllowedException e) { } catch (MethodNotAllowedException e) {
replyError(req, res, status = SC_METHOD_NOT_ALLOWED, responseBytes = replyError(req, res, status = SC_METHOD_NOT_ALLOWED,
messageOr(e, "Method Not Allowed"), e.caching(), e); messageOr(e, "Method Not Allowed"), e.caching(), e);
} catch (ResourceConflictException e) { } catch (ResourceConflictException e) {
replyError(req, res, status = SC_CONFLICT, messageOr(e, "Conflict"), responseBytes = replyError(req, res, status = SC_CONFLICT,
e.caching(), e); messageOr(e, "Conflict"), e.caching(), e);
} catch (PreconditionFailedException e) { } catch (PreconditionFailedException e) {
replyError(req, res, status = SC_PRECONDITION_FAILED, responseBytes = replyError(req, res, status = SC_PRECONDITION_FAILED,
messageOr(e, "Precondition Failed"), e.caching(), e); messageOr(e, "Precondition Failed"), e.caching(), e);
} catch (UnprocessableEntityException e) { } catch (UnprocessableEntityException e) {
replyError(req, res, status = 422, messageOr(e, "Unprocessable Entity"), responseBytes = replyError(req, res, status = 422,
e.caching(), e); messageOr(e, "Unprocessable Entity"), e.caching(), e);
} catch (NotImplementedException e) { } catch (NotImplementedException e) {
replyError(req, res, status = SC_NOT_IMPLEMENTED, responseBytes = replyError(req, res, status = SC_NOT_IMPLEMENTED,
messageOr(e, "Not Implemented"), e); messageOr(e, "Not Implemented"), e);
} catch (Exception e) { } catch (Exception e) {
status = SC_INTERNAL_SERVER_ERROR; status = SC_INTERNAL_SERVER_ERROR;
handleException(e, req, res); responseBytes = handleException(e, req, res);
} finally { } finally {
String metric = viewData != null && viewData.view != null String metric = viewData != null && viewData.view != null
? globals.metrics.view(viewData) ? globals.metrics.view(viewData)
@@ -401,6 +404,9 @@ public class RestApiServlet extends HttpServlet {
if (status >= SC_BAD_REQUEST) { if (status >= SC_BAD_REQUEST) {
globals.metrics.errorCount.increment(metric, status); globals.metrics.errorCount.increment(metric, status);
} }
if (responseBytes != -1) {
globals.metrics.responseBytes.record(metric, responseBytes);
}
globals.metrics.serverLatency.record( globals.metrics.serverLatency.record(
metric, metric,
System.nanoTime() - startNanos, System.nanoTime() - startNanos,
@@ -667,7 +673,7 @@ public class RestApiServlet extends HttpServlet {
throw new InstantiationException("Cannot make " + type); throw new InstantiationException("Cannot make " + type);
} }
public static void replyJson(@Nullable HttpServletRequest req, public static long replyJson(@Nullable HttpServletRequest req,
HttpServletResponse res, HttpServletResponse res,
Multimap<String, String> config, Multimap<String, String> config,
Object result) Object result)
@@ -683,7 +689,7 @@ public class RestApiServlet extends HttpServlet {
} }
w.write('\n'); w.write('\n');
w.flush(); w.flush();
replyBinaryResult(req, res, asBinaryResult(buf) return replyBinaryResult(req, res, asBinaryResult(buf)
.setContentType(JSON_TYPE) .setContentType(JSON_TYPE)
.setCharacterEncoding(UTF_8)); .setCharacterEncoding(UTF_8));
} }
@@ -752,7 +758,7 @@ public class RestApiServlet extends HttpServlet {
} }
@SuppressWarnings("resource") @SuppressWarnings("resource")
static void replyBinaryResult( static long replyBinaryResult(
@Nullable HttpServletRequest req, @Nullable HttpServletRequest req,
HttpServletResponse res, HttpServletResponse res,
BinaryResult bin) throws IOException { BinaryResult bin) throws IOException {
@@ -783,10 +789,13 @@ public class RestApiServlet extends HttpServlet {
} }
if (req == null || !"HEAD".equals(req.getMethod())) { if (req == null || !"HEAD".equals(req.getMethod())) {
try (OutputStream dst = res.getOutputStream()) { try (CountingOutputStream dst =
new CountingOutputStream(res.getOutputStream())) {
bin.writeTo(dst); bin.writeTo(dst);
return dst.getCount();
} }
} }
return 0;
} finally { } finally {
appResult.close(); appResult.close();
} }
@@ -993,7 +1002,7 @@ public class RestApiServlet extends HttpServlet {
viewData.pluginName, viewData.view.getClass()); viewData.pluginName, viewData.view.getClass());
} }
private static void handleException(Throwable err, HttpServletRequest req, private static long handleException(Throwable err, HttpServletRequest req,
HttpServletResponse res) throws IOException { HttpServletResponse res) throws IOException {
String uri = req.getRequestURI(); String uri = req.getRequestURI();
if (!Strings.isNullOrEmpty(req.getQueryString())) { if (!Strings.isNullOrEmpty(req.getQueryString())) {
@@ -1003,16 +1012,17 @@ public class RestApiServlet extends HttpServlet {
if (!res.isCommitted()) { if (!res.isCommitted()) {
res.reset(); res.reset();
replyError(req, res, SC_INTERNAL_SERVER_ERROR, "Internal server error", err); return replyError(req, res, SC_INTERNAL_SERVER_ERROR, "Internal server error", err);
} }
return 0;
} }
public static void replyError(HttpServletRequest req, HttpServletResponse res, public static long replyError(HttpServletRequest req, HttpServletResponse res,
int statusCode, String msg, @Nullable Throwable err) throws IOException { int statusCode, String msg, @Nullable Throwable err) throws IOException {
replyError(req, res, statusCode, msg, CacheControl.NONE, err); return replyError(req, res, statusCode, msg, CacheControl.NONE, err);
} }
public static void replyError(HttpServletRequest req, public static long replyError(HttpServletRequest req,
HttpServletResponse res, int statusCode, String msg, HttpServletResponse res, int statusCode, String msg,
CacheControl c, @Nullable Throwable err) throws IOException { CacheControl c, @Nullable Throwable err) throws IOException {
if (err != null) { if (err != null) {
@@ -1020,18 +1030,18 @@ public class RestApiServlet extends HttpServlet {
} }
configureCaching(req, res, null, null, c); configureCaching(req, res, null, null, c);
res.setStatus(statusCode); res.setStatus(statusCode);
replyText(req, res, msg); return replyText(req, res, msg);
} }
static void replyText(@Nullable HttpServletRequest req, static long replyText(@Nullable HttpServletRequest req,
HttpServletResponse res, String text) throws IOException { HttpServletResponse res, String text) throws IOException {
if ((req == null || isGetOrHead(req)) && isMaybeHTML(text)) { if ((req == null || isGetOrHead(req)) && isMaybeHTML(text)) {
replyJson(req, res, ImmutableMultimap.of("pp", "0"), new JsonPrimitive(text)); return replyJson(req, res, ImmutableMultimap.of("pp", "0"), new JsonPrimitive(text));
} else { } else {
if (!text.endsWith("\n")) { if (!text.endsWith("\n")) {
text += "\n"; text += "\n";
} }
replyBinaryResult(req, res, return replyBinaryResult(req, res,
BinaryResult.create(text).setContentType("text/plain")); BinaryResult.create(text).setContentType("text/plain"));
} }
} }

View File

@@ -91,6 +91,41 @@ public class DisabledMetricMaker extends MetricMaker {
}; };
} }
@Override
public Histogram0 newHistogram(String name, Description desc) {
return new Histogram0() {
@Override public void record(long value) {}
@Override public void remove() {}
};
}
@Override
public <F1> Histogram1<F1> newHistogram(String name, Description desc,
Field<F1> field1) {
return new Histogram1<F1>() {
@Override public void record(F1 field1, long value) {}
@Override public void remove() {}
};
}
@Override
public <F1, F2> Histogram2<F1, F2> newHistogram(String name, Description desc,
Field<F1> field1, Field<F2> field2) {
return new Histogram2<F1, F2>() {
@Override public void record(F1 field1, F2 field2, long value) {}
@Override public void remove() {}
};
}
@Override
public <F1, F2, F3> Histogram3<F1, F2, F3> newHistogram(String name,
Description desc, Field<F1> field1, Field<F2> field2, Field<F3> field3) {
return new Histogram3<F1, F2, F3>() {
@Override public void record(F1 field1, F2 field2, F3 field3, long value) {}
@Override public void remove() {}
};
}
@Override @Override
public <V> CallbackMetric0<V> newCallbackMetric(String name, public <V> CallbackMetric0<V> newCallbackMetric(String name,
Class<V> valueClass, Description desc) { Class<V> valueClass, Description desc) {

View File

@@ -0,0 +1,27 @@
// Copyright (C) 2015 The Android Open Source Project
//
// 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 com.google.gerrit.metrics;
import com.google.gerrit.extensions.registration.RegistrationHandle;
/**
* Measures the statistical distribution of values in a stream of data.
* <p>
* Suitable uses are "response size in bytes", etc.
*/
public abstract class Histogram0 implements RegistrationHandle {
/** Record a sample of a specified amount. */
public abstract void record(long value);
}

View File

@@ -0,0 +1,29 @@
// Copyright (C) 2015 The Android Open Source Project
//
// 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 com.google.gerrit.metrics;
import com.google.gerrit.extensions.registration.RegistrationHandle;
/**
* Measures the statistical distribution of values in a stream of data.
* <p>
* Suitable uses are "response size in bytes", etc.
*
* @param <F1> type of the field.
*/
public abstract class Histogram1<F1> implements RegistrationHandle {
/** Record a sample of a specified amount. */
public abstract void record(F1 field1, long value);
}

View File

@@ -0,0 +1,30 @@
// Copyright (C) 2015 The Android Open Source Project
//
// 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 com.google.gerrit.metrics;
import com.google.gerrit.extensions.registration.RegistrationHandle;
/**
* Measures the statistical distribution of values in a stream of data.
* <p>
* Suitable uses are "response size in bytes", etc.
*
* @param <F1> type of the field.
* @param <F2> type of the field.
*/
public abstract class Histogram2<F1, F2> implements RegistrationHandle {
/** Record a sample of a specified amount. */
public abstract void record(F1 field1, F2 field2, long value);
}

View File

@@ -0,0 +1,31 @@
// Copyright (C) 2015 The Android Open Source Project
//
// 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 com.google.gerrit.metrics;
import com.google.gerrit.extensions.registration.RegistrationHandle;
/**
* Measures the statistical distribution of values in a stream of data.
* <p>
* Suitable uses are "response size in bytes", etc.
*
* @param <F1> type of the field.
* @param <F2> type of the field.
* @param <F3> type of the field.
*/
public abstract class Histogram3<F1, F2, F3> implements RegistrationHandle {
/** Record a sample of a specified amount. */
public abstract void record(F1 field1, F2 field2, F3 field3, long value);
}

View File

@@ -46,6 +46,18 @@ public abstract class MetricMaker {
String name, Description desc, String name, Description desc,
Field<F1> field1, Field<F2> field2, Field<F3> field3); Field<F1> field1, Field<F2> field2, Field<F3> field3);
/** Metric statistical distribution of values. */
public abstract Histogram0 newHistogram(String name, Description desc);
public abstract <F1> Histogram1<F1> newHistogram(
String name, Description desc,
Field<F1> field1);
public abstract <F1, F2> Histogram2<F1, F2> newHistogram(
String name, Description desc,
Field<F1> field1, Field<F2> field2);
public abstract <F1, F2, F3> Histogram3<F1, F2, F3> newHistogram(
String name, Description desc,
Field<F1> field1, Field<F2> field2, Field<F3> field3);
/** /**
* Constant value that does not change. * Constant value that does not change.
* *

View File

@@ -0,0 +1,107 @@
// Copyright (C) 2015 The Android Open Source Project
//
// 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 com.google.gerrit.metrics.dropwizard;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker.HistogramImpl;
import com.codahale.metrics.Metric;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/** Abstract histogram broken down into buckets by {@link Field} values. */
abstract class BucketedHistogram implements BucketedMetric {
private final DropWizardMetricMaker metrics;
private final String name;
private final Description.FieldOrdering ordering;
protected final Field<?>[] fields;
protected final HistogramImpl total;
private final Map<Object, HistogramImpl> cells;
BucketedHistogram(DropWizardMetricMaker metrics, String name,
Description desc, Field<?>... fields) {
this.metrics = metrics;
this.name = name;
this.ordering = desc.getFieldOrdering();
this.fields = fields;
this.total = metrics.newHistogramImpl(name + "_total");
this.cells = new ConcurrentHashMap<>();
}
void doRemove() {
for (HistogramImpl c : cells.values()) {
c.remove();
}
total.remove();
metrics.remove(name);
}
HistogramImpl forceCreate(Object f1, Object f2) {
return forceCreate(ImmutableList.of(f1, f2));
}
HistogramImpl forceCreate(Object f1, Object f2, Object f3) {
return forceCreate(ImmutableList.of(f1, f2, f3));
}
HistogramImpl forceCreate(Object key) {
HistogramImpl c = cells.get(key);
if (c != null) {
return c;
}
synchronized (cells) {
c = cells.get(key);
if (c == null) {
c = metrics.newHistogramImpl(submetric(key));
cells.put(key, c);
}
return c;
}
}
private String submetric(Object key) {
return DropWizardMetricMaker.name(ordering, name, name(key));
}
abstract String name(Object key);
@Override
public Metric getTotal() {
return total.metric;
}
@Override
public Field<?>[] getFields() {
return fields;
}
@Override
public Map<Object, Metric> getCells() {
return Maps.transformValues(
cells,
new Function<HistogramImpl, Metric> () {
@Override
public Metric apply(HistogramImpl in) {
return in.metric;
}
});
}
}

View File

@@ -32,6 +32,10 @@ import com.google.gerrit.metrics.Counter3;
import com.google.gerrit.metrics.Description; import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Description.FieldOrdering; import com.google.gerrit.metrics.Description.FieldOrdering;
import com.google.gerrit.metrics.Field; import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.Histogram0;
import com.google.gerrit.metrics.Histogram1;
import com.google.gerrit.metrics.Histogram2;
import com.google.gerrit.metrics.Histogram3;
import com.google.gerrit.metrics.MetricMaker; import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer0; import com.google.gerrit.metrics.Timer0;
import com.google.gerrit.metrics.Timer1; import com.google.gerrit.metrics.Timer1;
@@ -226,6 +230,56 @@ public class DropWizardMetricMaker extends MetricMaker {
return new TimerImpl(name, registry.timer(name)); return new TimerImpl(name, registry.timer(name));
} }
@Override
public synchronized Histogram0 newHistogram(String name, Description desc) {
checkHistogramDescription(name, desc);
define(name, desc);
return newHistogramImpl(name);
}
@Override
public synchronized <F1> Histogram1<F1> newHistogram(String name,
Description desc, Field<F1> field1) {
checkHistogramDescription(name, desc);
HistogramImpl1<F1> m = new HistogramImpl1<>(this, name, desc, field1);
define(name, desc);
bucketed.put(name, m);
return m.histogram1();
}
@Override
public synchronized <F1, F2> Histogram2<F1, F2> newHistogram(String name,
Description desc, Field<F1> field1, Field<F2> field2) {
checkHistogramDescription(name, desc);
HistogramImplN m = new HistogramImplN(this, name, desc, field1, field2);
define(name, desc);
bucketed.put(name, m);
return m.histogram2();
}
@Override
public synchronized <F1, F2, F3> Histogram3<F1, F2, F3> newHistogram(
String name, Description desc,
Field<F1> field1, Field<F2> field2, Field<F3> field3) {
checkHistogramDescription(name, desc);
HistogramImplN m = new HistogramImplN(this, name, desc, field1, field2, field3);
define(name, desc);
bucketed.put(name, m);
return m.histogram3();
}
private static void checkHistogramDescription(String name, Description desc) {
checkMetricName(name);
checkArgument(!desc.isConstant(), "histogram must not be constant");
checkArgument(!desc.isGauge(), "histogram must not be a gauge");
checkArgument(!desc.isRate(), "histogram must not be a rate");
checkArgument(desc.isCumulative(), "histogram must be cumulative");
}
HistogramImpl newHistogramImpl(String name) {
return new HistogramImpl(name, registry.histogram(name));
}
@Override @Override
public <V> CallbackMetric0<V> newCallbackMetric( public <V> CallbackMetric0<V> newCallbackMetric(
String name, Class<V> valueClass, Description desc) { String name, Class<V> valueClass, Description desc) {
@@ -339,4 +393,25 @@ public class DropWizardMetricMaker extends MetricMaker {
registry.remove(name); registry.remove(name);
} }
} }
class HistogramImpl extends Histogram0 {
private final String name;
final com.codahale.metrics.Histogram metric;
private HistogramImpl(String name, com.codahale.metrics.Histogram metric) {
this.name = name;
this.metric = metric;
}
@Override
public void record(long value) {
metric.update(value);
}
@Override
public void remove() {
descriptions.remove(name);
registry.remove(name);
}
}
} }

View File

@@ -0,0 +1,52 @@
// Copyright (C) 2015 The Android Open Source Project
//
// 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 com.google.gerrit.metrics.dropwizard;
import com.google.common.base.Function;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.Histogram1;
/** Optimized version of {@link BucketedHistogram} for single dimension. */
class HistogramImpl1<F1> extends BucketedHistogram implements BucketedMetric {
HistogramImpl1(DropWizardMetricMaker metrics, String name,
Description desc, Field<F1> field1) {
super(metrics, name, desc, field1);
}
Histogram1<F1> histogram1() {
return new Histogram1<F1>() {
@Override
public void record(F1 field1, long value) {
total.record(value);
forceCreate(field1).record(value);
}
@Override
public void remove() {
doRemove();
}
};
}
@Override
String name(Object field1) {
@SuppressWarnings("unchecked")
Function<Object, String> fmt =
(Function<Object, String>) fields[0].formatter();
return fmt.apply(field1).replace('/', '-');
}
}

View File

@@ -0,0 +1,75 @@
// Copyright (C) 2015 The Android Open Source Project
//
// 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 com.google.gerrit.metrics.dropwizard;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.Histogram2;
import com.google.gerrit.metrics.Histogram3;
/** Generalized implementation of N-dimensional Histogram metrics. */
class HistogramImplN extends BucketedHistogram implements BucketedMetric {
HistogramImplN(DropWizardMetricMaker metrics, String name,
Description desc, Field<?>... fields) {
super(metrics, name, desc, fields);
}
<F1, F2> Histogram2<F1, F2> histogram2() {
return new Histogram2<F1, F2>() {
@Override
public void record(F1 field1, F2 field2, long value) {
total.record(value);
forceCreate(field1, field2).record(value);
}
@Override
public void remove() {
doRemove();
}
};
}
<F1, F2, F3> Histogram3<F1, F2, F3> histogram3() {
return new Histogram3<F1, F2, F3>() {
@Override
public void record(F1 field1, F2 field2, F3 field3, long value) {
total.record(value);
forceCreate(field1, field2, field3).record(value);
}
@Override
public void remove() {
doRemove();
}
};
}
@SuppressWarnings("unchecked")
@Override
String name(Object key) {
ImmutableList<Object> keyList = (ImmutableList<Object>) key;
String[] parts = new String[fields.length];
for (int i = 0; i < fields.length; i++) {
Function<Object, String> fmt =
(Function<Object, String>) fields[i].formatter();
parts[i] = fmt.apply(keyList.get(i)).replace('/', '-');
}
return Joiner.on('/').join(parts);
}
}

View File

@@ -22,6 +22,7 @@ import com.google.gerrit.metrics.Field;
import com.codahale.metrics.Counter; import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge; import com.codahale.metrics.Gauge;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter; import com.codahale.metrics.Meter;
import com.codahale.metrics.Metric; import com.codahale.metrics.Metric;
import com.codahale.metrics.Snapshot; import com.codahale.metrics.Snapshot;
@@ -56,7 +57,9 @@ class MetricJson {
Double p99_9; Double p99_9;
Double min; Double min;
Double avg;
Double max; Double max;
Double sum;
Double std_dev; Double std_dev;
List<FieldJson> fields; List<FieldJson> fields;
@@ -123,6 +126,24 @@ class MetricJson {
min = s.getMin() / div; min = s.getMin() / div;
max = s.getMax() / div; max = s.getMax() / div;
std_dev = s.getStdDev() / div; std_dev = s.getStdDev() / div;
} else if (metric instanceof Histogram) {
Histogram m = (Histogram) metric;
Snapshot s = m.getSnapshot();
count = m.getCount();
p50 = s.getMedian();
p75 = s.get75thPercentile();
p95 = s.get95thPercentile();
p98 = s.get98thPercentile();
p99 = s.get99thPercentile();
p99_9 = s.get999thPercentile();
min = (double) s.getMin();
avg = (double) s.getMean();
max = (double) s.getMax();
sum = s.getMean() * m.getCount();
std_dev = s.getStdDev();
} }
} }

View File

@@ -20,6 +20,7 @@ import com.google.gerrit.metrics.Counter1;
import com.google.gerrit.metrics.Description; import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Description.Units; import com.google.gerrit.metrics.Description.Units;
import com.google.gerrit.metrics.Field; import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.Histogram1;
import com.google.gerrit.metrics.MetricMaker; import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer1; import com.google.gerrit.metrics.Timer1;
import com.google.inject.Inject; import com.google.inject.Inject;
@@ -39,6 +40,7 @@ public class UploadPackMetricsHook implements PostUploadHook {
private final Timer1<Operation> counting; private final Timer1<Operation> counting;
private final Timer1<Operation> compressing; private final Timer1<Operation> compressing;
private final Timer1<Operation> writing; private final Timer1<Operation> writing;
private final Histogram1<Operation> packBytes;
@Inject @Inject
UploadPackMetricsHook(MetricMaker metricMaker) { UploadPackMetricsHook(MetricMaker metricMaker) {
@@ -70,6 +72,13 @@ public class UploadPackMetricsHook implements PostUploadHook {
.setCumulative() .setCumulative()
.setUnit(Units.MILLISECONDS), .setUnit(Units.MILLISECONDS),
operation); operation);
packBytes = metricMaker.newHistogram(
"git/upload-pack/pack_bytes",
new Description("Distribution of sizes of packs sent to clients")
.setCumulative()
.setUnit(Units.BYTES),
operation);
} }
@Override @Override
@@ -84,5 +93,6 @@ public class UploadPackMetricsHook implements PostUploadHook {
counting.record(op, stats.getTimeCounting(), MILLISECONDS); counting.record(op, stats.getTimeCounting(), MILLISECONDS);
compressing.record(op, stats.getTimeCompressing(), MILLISECONDS); compressing.record(op, stats.getTimeCompressing(), MILLISECONDS);
writing.record(op, stats.getTimeWriting(), MILLISECONDS); writing.record(op, stats.getTimeWriting(), MILLISECONDS);
packBytes.record(op, stats.getTotalBytes());
} }
} }

View File

@@ -25,6 +25,10 @@ import com.google.gerrit.metrics.Counter2;
import com.google.gerrit.metrics.Counter3; import com.google.gerrit.metrics.Counter3;
import com.google.gerrit.metrics.Description; import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.Field; import com.google.gerrit.metrics.Field;
import com.google.gerrit.metrics.Histogram0;
import com.google.gerrit.metrics.Histogram1;
import com.google.gerrit.metrics.Histogram2;
import com.google.gerrit.metrics.Histogram3;
import com.google.gerrit.metrics.MetricMaker; import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer0; import com.google.gerrit.metrics.Timer0;
import com.google.gerrit.metrics.Timer1; import com.google.gerrit.metrics.Timer1;
@@ -117,6 +121,41 @@ class PluginMetricMaker extends MetricMaker implements LifecycleListener {
return m; return m;
} }
@Override
public Histogram0 newHistogram(String name, Description desc) {
Histogram0 m = root.newHistogram(prefix + name, desc);
cleanup.add(m);
return m;
}
@Override
public <F1> Histogram1<F1> newHistogram(
String name, Description desc,
Field<F1> field1) {
Histogram1<F1> m = root.newHistogram(prefix + name, desc, field1);
cleanup.add(m);
return m;
}
@Override
public <F1, F2> Histogram2<F1, F2> newHistogram(
String name, Description desc,
Field<F1> field1, Field<F2> field2) {
Histogram2<F1, F2> m = root.newHistogram(prefix + name, desc, field1, field2);
cleanup.add(m);
return m;
}
@Override
public <F1, F2, F3> Histogram3<F1, F2, F3> newHistogram(
String name, Description desc,
Field<F1> field1, Field<F2> field2, Field<F3> field3) {
Histogram3<F1, F2, F3> m =
root.newHistogram(prefix + name, desc, field1, field2, field3);
cleanup.add(m);
return m;
}
@Override @Override
public <V> CallbackMetric0<V> newCallbackMetric( public <V> CallbackMetric0<V> newCallbackMetric(
String name, Class<V> valueClass, Description desc) { String name, Class<V> valueClass, Description desc) {