Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reinstate time-to-first-byte-ms metric. #575

Merged
merged 5 commits into from
Jan 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (C) 2013-2019 Expedia Inc.
Copyright (C) 2013-2020 Expedia Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -65,13 +65,6 @@ interface Factory {
*/
Origin getOrigin();

/**
* Returns time to first byte in milliseconds.
*
* @return time to first byte in milliseconds
*/
long getTimeToFirstByteMillis();

/**
* Register a listener connection state events.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (C) 2013-2018 Expedia Inc.
Copyright (C) 2013-2020 Expedia Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -48,21 +48,21 @@ public void stopAndRecord() {
}
}

private final Timer requestLatency;
private final Timer applicationLatency;
private final Timer requestTimer;
private final Timer applicationTimer;

/**
* Constructor.
*
* @param requestLatency a timer
* @param applicationLatency a timer
* @param requestTimer a timer
* @param applicationTimer a timer
*/
public AggregateTimer(Timer requestLatency, Timer applicationLatency) {
this.requestLatency = requestLatency;
this.applicationLatency = applicationLatency;
public AggregateTimer(Timer requestTimer, Timer applicationTimer) {
this.requestTimer = requestTimer;
this.applicationTimer = applicationTimer;
}

public Stopper time() {
return new Stopper(requestLatency.time(), applicationLatency.time());
return new Stopper(requestTimer.time(), applicationTimer.time());
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (C) 2013-2018 Expedia Inc.
Copyright (C) 2013-2020 Expedia Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -33,10 +33,15 @@ public interface OriginStats {
void requestError();

/**
* Starts request latency timer.
* Returns a request latency timer.
*/
AggregateTimer requestLatencyTimer();

/**
* Returns a time-to-first-byte timer.
*/
AggregateTimer timeToFirstByteTimer();

/**
* records a response with a status code.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (C) 2013-2018 Expedia Inc.
Copyright (C) 2013-2020 Expedia Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -32,6 +32,7 @@ public class ApplicationMetrics {
private final MetricRegistry applicationMetrics;
private final MetricRegistry requestScope;
private final Timer requestLatencyTimer;
private final Timer requestTimeToFirstByteTimer;
private final Meter requestSuccessMeter;
private final Meter requestErrorMeter;
private final Meter status200OkMeter;
Expand All @@ -52,6 +53,7 @@ public ApplicationMetrics(Id appId, MetricRegistry metricRegistry) {

this.requestScope = this.applicationMetrics.scope("requests");
this.requestLatencyTimer = this.requestScope.timer("latency");
this.requestTimeToFirstByteTimer = this.requestScope.timer("time-to-first-byte");
this.requestSuccessMeter = this.requestScope.meter("success-rate");
this.requestErrorMeter = this.requestScope.meter("error-rate");
this.status200OkMeter = this.requestScope.meter(name("response", statusCodeName(200)));
Expand All @@ -73,14 +75,19 @@ public void requestError() {
}

/**
* Starts request latency timer.
*
* @return timer context for request latency
* Returns request latency timer.
*/
public Timer requestLatencyTimer() {
return requestLatencyTimer;
}

/**
* Returns request time-to-first-byte timer.
*/
public Timer requestTimeToFirstByteTimer() {
return requestTimeToFirstByteTimer;
}

/**
* Returns the metrics registry used.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (C) 2013-2019 Expedia Inc.
Copyright (C) 2013-2020 Expedia Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -50,6 +50,7 @@ public class OriginMetrics implements OriginStats {
private final Meter requestSuccessMeter;
private final Meter requestErrorMeter;
private final Timer requestLatency;
private final Timer timeToFirstByte;
private final Meter status200OkMeter;
private final Meter errorsCatchAll;

Expand All @@ -60,7 +61,7 @@ public class OriginMetrics implements OriginStats {
* Constructor.
*
* @param applicationMetrics application metrics
* @param origin an origin
* @param originId an origin
*/
public OriginMetrics(ApplicationMetrics applicationMetrics, String originId) {
this.applicationMetrics = requireNonNull(applicationMetrics);
Expand All @@ -72,6 +73,7 @@ public OriginMetrics(ApplicationMetrics applicationMetrics, String originId) {
this.requestSuccessMeter = this.registry.meter(name(this.requestMetricPrefix, "success-rate"));
this.requestErrorMeter = this.registry.meter(name(this.requestMetricPrefix, "error-rate"));
this.requestLatency = this.registry.timer(name(this.requestMetricPrefix, "latency"));
this.timeToFirstByte = this.registry.timer(name(this.requestMetricPrefix, "time-to-first-byte"));
this.status200OkMeter = this.registry.meter(name(this.requestMetricPrefix, "response", statusCodeName(200)));
this.errorsCatchAll = this.registry.meter(name(this.requestMetricPrefix, "response.status.5xx"));

Expand All @@ -81,7 +83,8 @@ public OriginMetrics(ApplicationMetrics applicationMetrics, String originId) {
/**
* Create a new OriginMetrics.
*
* @param origin an origin
* @param appId application ID
* @param originId an origin
* @param metricRegistry a metrics registry
* @return a new OriginMetrics
*/
Expand Down Expand Up @@ -134,6 +137,11 @@ public AggregateTimer requestLatencyTimer() {
return new AggregateTimer(requestLatency, applicationMetrics.requestLatencyTimer());
}

@Override
public AggregateTimer timeToFirstByteTimer() {
return new AggregateTimer(timeToFirstByte, applicationMetrics.requestTimeToFirstByteTimer());
}

private int httpStatusCodeClass(int code) {
if (code < 100 || code >= 600) {
return 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (C) 2013-2019 Expedia Inc.
Copyright (C) 2013-2020 Expedia Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (C) 2013-2019 Expedia Inc.
Copyright (C) 2013-2020 Expedia Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -62,11 +62,6 @@ public Origin getOrigin() {
return nettyConnection.getOrigin();
}

@Override
public long getTimeToFirstByteMillis() {
return nettyConnection.getTimeToFirstByteMillis();
}

@Override
public void addConnectionListener(Listener listener) {
nettyConnection.addConnectionListener(listener);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (C) 2013-2019 Expedia Inc.
Copyright (C) 2013-2020 Expedia Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -223,6 +223,7 @@ public ConnectionPool.Stats stats() {

@VisibleForTesting
private class ConnectionPoolStats implements Stats {

@Override
public int availableConnectionCount() {
return availableConnections.size();
Expand Down Expand Up @@ -263,7 +264,6 @@ public int connectionsInEstablishment() {
return connectionsInEstablishment.get();
}


@Override
public String toString() {
return MoreObjects.toStringHelper(this)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (C) 2013-2019 Expedia Inc.
Copyright (C) 2013-2020 Expedia Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (C) 2013-2019 Expedia Inc.
Copyright (C) 2013-2020 Expedia Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -64,11 +64,6 @@ public Origin getOrigin() {
return this.origin;
}

@Override
public long getTimeToFirstByteMillis() {
return 0;
}

@Override
public void addConnectionListener(Listener listener) {
listeners.addListener(listener);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (C) 2013-2019 Expedia Inc.
Copyright (C) 2013-2020 Expedia Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -32,23 +32,21 @@
import reactor.core.publisher.Flux;

import java.util.Optional;
import java.util.concurrent.TimeUnit;

import static com.google.common.base.Objects.toStringHelper;
import static java.util.Objects.requireNonNull;

/**
* A connection using a netty channel.
*/
public class NettyConnection implements Connection, TimeToFirstByteListener {
public class NettyConnection implements Connection {
private static final AttributeKey<Object> CLOSED_BY_STYX = AttributeKey.newInstance("CLOSED_BY_STYX");
private static final int IGNORED_PORT_NUMBER = -1;

private final Origin origin;
private final Channel channel;
private final HttpRequestOperationFactory requestOperationFactory;

private volatile long timeToFirstByteMs;
private final Announcer<Listener> listeners = Announcer.to(Listener.class);


Expand All @@ -68,7 +66,6 @@ public NettyConnection(Origin origin, Channel channel, HttpRequestOperationFacto
this.origin = requireNonNull(origin);
this.channel = requireNonNull(channel);
this.requestOperationFactory = requestOperationFactory;
this.channel.pipeline().addLast(new TimeToFirstByteHandler(this));
this.channel.closeFuture().addListener(future ->
listeners.announce().connectionClosed(NettyConnection.this));
addChannelHandlers(channel, httpConfig, sslContext, sendSni, sniHost.orElse(origin.host()));
Expand Down Expand Up @@ -115,11 +112,6 @@ public Origin getOrigin() {
return this.origin;
}

@Override
public long getTimeToFirstByteMillis() {
return this.timeToFirstByteMs;
}

@Override
public void addConnectionListener(Listener listener) {
this.listeners.addListener(listener);
Expand All @@ -133,11 +125,6 @@ public void close() {
}
}

@Override
public void notifyTimeToFirstByte(long timeToFirstByte, TimeUnit timeUnit) {
this.timeToFirstByteMs = timeUnit.toMillis(timeToFirstByte);
}

@Override
public String toString() {
return toStringHelper(this)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright (C) 2013-2018 Expedia Inc.
Copyright (C) 2013-2020 Expedia Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -20,6 +20,7 @@
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.LastHttpContent;
import org.slf4j.Logger;
Expand All @@ -38,6 +39,9 @@ class RequestsToOriginMetricsCollector extends ChannelDuplexHandler {

private final OriginStats originStats;
private volatile AggregateTimer.Stopper requestLatencyTiming;
private volatile AggregateTimer.Stopper timeToFirstByteTiming;
private volatile boolean firstContentChunkReceived;


/**
* Constructs a new instance.
Expand All @@ -62,6 +66,11 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception
updateHttpResponseCounters(code);
}

if (msg instanceof HttpContent && !firstContentChunkReceived) {
stopAndRecordTimeToFirstByte();
firstContentChunkReceived = true;
}

if (msg instanceof LastHttpContent) {
stopAndRecordLatency();
}
Expand All @@ -73,6 +82,8 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
if (msg instanceof io.netty.handler.codec.http.HttpRequest) {
requestLatencyTiming = originStats.requestLatencyTimer().time();
timeToFirstByteTiming = originStats.timeToFirstByteTimer().time();
firstContentChunkReceived = false;
}
super.write(ctx, msg, promise);
}
Expand All @@ -93,6 +104,7 @@ private void updateHttpResponseCounters(int statusCode) {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
stopAndRecordLatency();
stopAndRecordTimeToFirstByte();
super.exceptionCaught(ctx, cause);
}

Expand All @@ -106,4 +118,12 @@ private void stopAndRecordLatency() {
LOG.warn("Attempted to stop timer and record latency when no timing had begun");
}
}

private void stopAndRecordTimeToFirstByte() {
if (timeToFirstByteTiming != null) {
timeToFirstByteTiming.stopAndRecord();
} else {
LOG.warn("Attempted to stop timer and record time-to-first-byte when no timing had begun");
}
}
}
Loading