Recipes
Java SDK- verbosed
1min
1. Meter examples
Java
1package demo;
2
3import com.amberflo.metering.ingest.MeteringContext;
4import com.amberflo.metering.ingest.extensions.ServiceMetering;
5import com.amberflo.metering.ingest.meter_message.Domain;
6import com.amberflo.metering.ingest.meter_message.MeterMessage;
7import com.amberflo.metering.ingest.meter_message.MeterMessageBuilder;
8
9import java.time.LocalDateTime;
10import java.time.ZoneId;
11import java.util.HashMap;
12import java.util.Map;
13
14import com.amberflo.metering.ingest.meter_message.Region;
15
16import static com.amberflo.metering.ingest.MeteringContext.metering;
17import static com.amberflo.metering.ingest.extensions.ServiceMetering.serviceMetering;
18import static com.amberflo.metering.ingest.extensions.CustomerMetering.customerMetering;
19
20/**
21 * In this app we describe different ways to call the metering services:
22 * 1. Builder.
23 * 2. Factory.
24 * 3. Templates.
25 *
26 * The example itself used the {@link Domain#Prod} configuration to setup the metering client.
27 *
28 * For more info about each of our api please see the java doc.
29 */
30public class MeteringExamples {
31 private final static String METER_NAME = "TrancsactionCount";
32 private final static int METER_VALUE = 3;
33 private final static String CUSTOMER_ID = "YWJjNDU2";
34 private final static String SERVICE_CALL = "process-request";
35 private final static String SERVICE_NAME = "magic-service";
36 private final static LocalDateTime EVENT_TIME = LocalDateTime.now();
37 private final static Double MB_USED = 15.5;
38 private final static double PROCESSING_TIME_MILLIS = 100;
39
40 private final static boolean ERROR_FLAG = true;
41
42 private final static Map<String, String> EXTRA_DIMENSIONS_MAP = null;
43
44
45 public static void main(String[] args) throws Exception {
46 // Step 1 - setting up the domain (prod dev)
47 // You need to set the "metering domain" system env var to prod.
48 System.setProperty(MeteringContext.METERING_DOMAIN, Domain.Prod.name());
49
50 // Step 2 - send some meters to amberflo.
51 sendSomeMeters();
52
53 // Step 3 - flush and close the metering client.
54 // For efficiency, we send meters to amberflo in batches. You can configure the max batch size in
55 // the prod/def metering.json file. Having said that, even if you use a max batch size of 1, you still want
56 // to call this method, especially if use configured 'isAsync' to true.
57 MeteringContext.flushAndClose();
58
59 // Comment: 'Metering' is autoClosable - so instead of step 2 and 3, you can also send meters this way:
60 try (final MeteringContext context = MeteringContext.getContext()) {
61 // templates are just and abstraction layer on the top of the metering.
62 customerMetering().signUp(CUSTOMER_ID, EVENT_TIME);
63 }
64 }
65
66 /**
67 * Here we describe the 3 methods to directly send meters:
68 * 1. Builder.
69 * 2. Factory.
70 * 3. Templates.
71 */
72 private static void sendSomeMeters() {
73 // Option 1: Create a custom meter with a builder.
74 // This option gives you more flexibility regarding the way you construct your meter event, but it
75 // requires more effort on your side, and might be more errors prone than other options.
76 // Use it only if you need a completely custom meter.
77 final MeterMessage meter = MeterMessageBuilder
78 .createInstance(METER_NAME, EVENT_TIME, CUSTOMER_ID)
79 .setMeterValue(METER_VALUE)
80 .build();
81 metering().meter(meter);
82
83 // Look at this method to see a more advanced example for how to set a meter this way.
84 customBuilderAdvanced();
85
86
87 // Option 2: Create a custom message with a factory
88 // A factory is a bit more structured way of creating a meter but less flexible. It lets you send
89 // a meter which contains the:
90 // a. The customer id - Mandatory (unless you invoke the method within a thread context with
91 // customer info). This is the id of your customer who made a call to your service (let's say).
92 // b. The meter name and value - Required.
93 // c. The start_time - Optional. this can be a start time of a call, or the event time which is relevant
94 // for the meter. If the start time is null then the event time will by the current time when sending
95 // the meter.
96 // d. The extra dimensions map - Optional. this is just a map of string keys to string values containing
97 // important dimensions.
98 metering().meter(CUSTOMER_ID, METER_NAME, METER_VALUE, EVENT_TIME, EXTRA_DIMENSIONS_MAP);
99 metering().meter(CUSTOMER_ID, METER_NAME, METER_VALUE, EVENT_TIME, null);
100
101
102 // Option 3: Templates
103 // Templates are the most convenient but least flexible option of creating meters explicitly. Basically
104 // templates are predefined meter types, with a predefined meter name, and predefined meter structure.
105 //
106 // Why do we need templates ?
107 // Let's assume we want to create meters for a service. What would we try to measure ? ... probably we would
108 // want to record commons things such as 'service calls', 'data usage' or 'processing time'. Now, we don't
109 // want you to reinvent the will for such common use cases, so we created a few domain-specific factories that
110 // allows you to create meters more easily.
111
112 // Option 3.1 - Service-Metering:
113 // use the serviceMetering to create
114 serviceMetering().call(CUSTOMER_ID, SERVICE_CALL, EVENT_TIME);
115 serviceMetering().processingTime(CUSTOMER_ID, SERVICE_CALL, PROCESSING_TIME_MILLIS, EVENT_TIME);
116 serviceMetering().dataUsage(CUSTOMER_ID, SERVICE_CALL, MB_USED, EVENT_TIME);
117 // See the method below for more advanced\detailed examples of using the meters.
118 serviceMeteringAdvanced();
119
120 // Option 3.2 - Customer-Metering.
121 // This templates allow you to create customer (client) related meters.
122 customerMetering().signUp(CUSTOMER_ID, EVENT_TIME);
123 customerMetering().onboarded(CUSTOMER_ID, EVENT_TIME);
124 customerMetering().offboarded(CUSTOMER_ID, EVENT_TIME);
125 customerMetering().login(CUSTOMER_ID, EVENT_TIME);
126 // This meter described a case a customer registered to your service, but you rejected the registration
127 // (false-data or a malicious customer for example).
128 customerMetering().onboardingRejected(CUSTOMER_ID, EVENT_TIME);
129 }
130
131 private static void customBuilderAdvanced() {
132 final Map<String, String> sessionInfo = new HashMap<>();
133 sessionInfo.put("session", "789");
134
135 final Map<String, String> countryAndStateInfo = new HashMap<>();
136 sessionInfo.put("country", "US");
137 sessionInfo.put("state", "WA");
138
139 final MeterMessage meter = MeterMessageBuilder
140 .createInstance(METER_NAME, EVENT_TIME, CUSTOMER_ID)
141 // meter value is actually optional param. If not set the the meter value will be 1.
142 .setMeterValue(METER_VALUE)
143 // if you want to capture the end time of the meter event (now), and measure the duration of
144 // it, you can use the this method which will set up the duration as the time diff in millis between
145 // now and the START_TIME. Calling this method will set the meter-type to "Millis".
146 .captureEndTimeAndDuration() // in our case the duration should be ~3 minutes (in millis).
147 // meter type isn't currently in use by amberflo. Yet if you feel the need to have it
148 // use it ... we will notice that and adapt our system accordingly.
149 .setMeterType("millis")
150 // Set up service related data.
151 // Service name is a param to the 'DirectMeteringClient', nevertheless, you can override it here.
152 .setServiceName(SERVICE_NAME)
153 .setServiceCall(SERVICE_CALL)
154 // If you want to mark your meter as error related you can call.
155 .asError() /* or */ .asError(IllegalArgumentException.class)
156 // you can set up a region.
157 .setRegion(Region.US_West)
158 // The dimensions Map gives you the option to add more properties to your meter, which don't
159 // exist as part of the predefined methods of the builder.
160 // For example, let's assume you want to add session info to your meter to track a process
161 // or customer request e2e, you can add this dimensions this way:
162 .setDimensionsMap(sessionInfo)
163 // Another example for custom dimensions:
164 // Let's assume you want to measure customer related events. As part of that you want to partition
165 // your customers by country and state. So you can add these two as custom attributes:
166 .setDimensionsMap(countryAndStateInfo)
167 // ^^ it's ok call setDimensionsMap multiple times as long as there is no intersection between
168 // the keys of the maps.
169 .build();
170 metering().meter(meter);
171 }
172
173 /**
174 * The service-metering template contains the following:
175 *
176 * {@link ServiceMetering#CALL}:
177 * 1. CallCompleted (successfully).
178 * 2. CallError.
179 * 3. Call - this one is to try and measure a call regardless if it completed successfully or with an error.
180 * All of these types of calls will produce a {@link ServiceMetering#CALL} meter as they indicates the event of finish
181 * handling a call (with or without an error).
182 *
183 * Other types of calls:
184 * There are 3 other more specific type of call which will produce different meter
185 * 1. CallStarted - Will produce a {@link ServiceMetering#CALL_STARTED} meter. Use this event if you want to
186 * have different meters for the start and the end of a call.
187 * 2. DataUsage - measure data used by the client (in Mb). Will produce a {@link ServiceMetering#CALL_DATA_USAGE} meter.
188 * 3. ProcessingTime - Time it took to process a service call request (in millis). Will produce a
189 * {@link ServiceMetering#CALL_PROCESSING_TIME} meter.
190 */
191 private static void serviceMeteringAdvanced() {
192 // 'call' will produce a meter called "Call" and it can be used in many ways:
193 serviceMetering().call(CUSTOMER_ID, SERVICE_CALL, EVENT_TIME);
194 serviceMetering().call(CUSTOMER_ID, SERVICE_CALL, ERROR_FLAG, EVENT_TIME); // if you want to mark it as an error
195 serviceMetering().call(CUSTOMER_ID, SERVICE_CALL, ERROR_FLAG, IllegalAccessError.class, EVENT_TIME); // error + error type
196 // The SERVICE_CALL in the example above isn't the meter name but a dimension.
197
198 // If you find it more convenient you can also record a service call using
199 serviceMetering().callCompleted(CUSTOMER_ID, SERVICE_CALL, EVENT_TIME); // To mark the end of a call that completed successfully.
200 serviceMetering().callError(CUSTOMER_ID, SERVICE_CALL, EVENT_TIME); // To mark the end of a call that completed with an error.
201 // We will see soon how these can be used.
202
203
204 // 'callStarted' as the name suggests, measure an event of start handling a service call regardless of the
205 // results (the call completed successfully or not).
206 serviceMetering().callStarted(CUSTOMER_ID, SERVICE_CALL, EVENT_TIME);
207
208 // 'processingTime' - measures the time it took to process the call in millis.
209 serviceMetering().processingTime(CUSTOMER_ID, SERVICE_CALL, PROCESSING_TIME_MILLIS, EVENT_TIME);
210
211
212 // 'dataUsage' - as the name suggests, measures the data returned to the client or used by the call in Mb.
213 serviceMetering().dataUsage(CUSTOMER_ID, SERVICE_CALL, MB_USED, EVENT_TIME);
214
215
216 // Examples of using multiple of the calls above:
217 final Runnable methodToRun = () -> {
218 try {
219 Thread.sleep(300);
220 } catch (InterruptedException e) {
221 e.printStackTrace();
222 }
223 };
224 serviceMeteringMultiCalls(methodToRun);
225 }
226
227 /**
228 * You can have a similar decorator/interceptor method to this one in your code.
229 */
230 private static void serviceMeteringMultiCalls(final Runnable runnable) {
231 final LocalDateTime startTime = LocalDateTime.now();
232 LocalDateTime endTime = startTime;
233 try {
234 serviceMetering().callStarted(CUSTOMER_ID, SERVICE_CALL, startTime);
235
236 runnable.run();
237
238 endTime = LocalDateTime.now();
239 serviceMetering().callCompleted(CUSTOMER_ID, SERVICE_CALL, endTime);
240 } catch (final Exception e) {
241 endTime = LocalDateTime.now();
242 serviceMetering().callError(CUSTOMER_ID, SERVICE_CALL, e.getClass(), endTime);
243 } finally {
244 final long durationInMillis =
245 endTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() -
246 startTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
247
248 serviceMetering().processingTime(CUSTOMER_ID, SERVICE_CALL, durationInMillis, endTime);
249 }
250 }
251}
Updated 11 Sep 2024
Did this page help you?