1 /*
2  * Copyright 2018, OpenCensus Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package io.opencensus.common;
18 
19 import java.nio.ByteBuffer;
20 import java.nio.ByteOrder;
21 
22 /**
23  * A service class to encode/decode {@link ServerStats} as defined by the spec.
24  *
25  * <p>See <a
26  * href="https://github.com/census-instrumentation/opencensus-specs/blob/master/encodings/CensusServerStatsEncoding.md">opencensus-server-stats-specs</a>
27  * for encoding {@code ServerStats}
28  *
29  * <p>Use {@code ServerStatsEncoding.toBytes(ServerStats stats)} to encode.
30  *
31  * <p>Use {@code ServerStatsEncoding.parseBytes(byte[] serialized)} to decode.
32  *
33  * @since 0.16
34  */
35 public final class ServerStatsEncoding {
36 
ServerStatsEncoding()37   private ServerStatsEncoding() {}
38 
39   /**
40    * The current encoding version. The value is {@value #CURRENT_VERSION}
41    *
42    * @since 0.16
43    */
44   public static final byte CURRENT_VERSION = (byte) 0;
45 
46   /**
47    * Encodes the {@link ServerStats} as per the Opencensus Summary Span specification.
48    *
49    * @param stats {@code ServerStats} to encode.
50    * @return encoded byte array.
51    * @since 0.16
52    */
toBytes(ServerStats stats)53   public static byte[] toBytes(ServerStats stats) {
54     // Should this be optimized to not include invalid values?
55 
56     ByteBuffer bb = ByteBuffer.allocate(ServerStatsFieldEnums.getTotalSize() + 1);
57     bb.order(ByteOrder.LITTLE_ENDIAN);
58 
59     // put version
60     bb.put(CURRENT_VERSION);
61 
62     bb.put((byte) ServerStatsFieldEnums.Id.SERVER_STATS_LB_LATENCY_ID.value());
63     bb.putLong(stats.getLbLatencyNs());
64 
65     bb.put((byte) ServerStatsFieldEnums.Id.SERVER_STATS_SERVICE_LATENCY_ID.value());
66     bb.putLong(stats.getServiceLatencyNs());
67 
68     bb.put((byte) ServerStatsFieldEnums.Id.SERVER_STATS_TRACE_OPTION_ID.value());
69     bb.put(stats.getTraceOption());
70     return bb.array();
71   }
72 
73   /**
74    * Decodes serialized byte array to create {@link ServerStats} as per Opencensus Summary Span
75    * specification.
76    *
77    * @param serialized encoded {@code ServerStats} in byte array.
78    * @return decoded {@code ServerStats}. null if decoding fails.
79    * @since 0.16
80    */
parseBytes(byte[] serialized)81   public static ServerStats parseBytes(byte[] serialized)
82       throws ServerStatsDeserializationException {
83     final ByteBuffer bb = ByteBuffer.wrap(serialized);
84     bb.order(ByteOrder.LITTLE_ENDIAN);
85     long serviceLatencyNs = 0L;
86     long lbLatencyNs = 0L;
87     byte traceOption = (byte) 0;
88 
89     // Check the version first.
90     if (!bb.hasRemaining()) {
91       throw new ServerStatsDeserializationException("Serialized ServerStats buffer is empty");
92     }
93     byte version = bb.get();
94 
95     if (version > CURRENT_VERSION || version < 0) {
96       throw new ServerStatsDeserializationException("Invalid ServerStats version: " + version);
97     }
98 
99     while (bb.hasRemaining()) {
100       ServerStatsFieldEnums.Id id = ServerStatsFieldEnums.Id.valueOf((int) bb.get() & 0xFF);
101       if (id == null) {
102         // Skip remaining;
103         bb.position(bb.limit());
104       } else {
105         switch (id) {
106           case SERVER_STATS_LB_LATENCY_ID:
107             lbLatencyNs = bb.getLong();
108             break;
109           case SERVER_STATS_SERVICE_LATENCY_ID:
110             serviceLatencyNs = bb.getLong();
111             break;
112           case SERVER_STATS_TRACE_OPTION_ID:
113             traceOption = bb.get();
114             break;
115         }
116       }
117     }
118     try {
119       return ServerStats.create(lbLatencyNs, serviceLatencyNs, traceOption);
120     } catch (IllegalArgumentException e) {
121       throw new ServerStatsDeserializationException(
122           "Serialized ServiceStats contains invalid values: " + e.getMessage());
123     }
124   }
125 }
126