Adjust Tx rate limiter for unused transactions
[controller.git] / opendaylight / md-sal / sal-distributed-datastore / src / main / java / org / opendaylight / controller / cluster / datastore / TransactionRateLimitingCallback.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.controller.cluster.datastore;
10
11 import com.codahale.metrics.Snapshot;
12 import com.codahale.metrics.Timer;
13 import com.google.common.base.Preconditions;
14 import java.util.concurrent.TimeUnit;
15 import org.opendaylight.controller.cluster.datastore.utils.ActorContext;
16 import org.slf4j.Logger;
17 import org.slf4j.LoggerFactory;
18
19 /**
20  * TransactionRateLimitingCallback computes the new transaction rate limit on the successful completion of a
21  * transaction
22  */
23 public class TransactionRateLimitingCallback implements OperationCallback{
24
25     private static final Logger LOG = LoggerFactory.getLogger(TransactionRateLimitingCallback.class);
26     private static final String COMMIT = "commit";
27
28     private final Timer commitTimer;
29     private final ActorContext actorContext;
30     private Timer.Context timerContext;
31
32     TransactionRateLimitingCallback(ActorContext actorContext){
33         this.actorContext = actorContext;
34         commitTimer = actorContext.getOperationTimer(COMMIT);
35     }
36
37     @Override
38     public void run() {
39         timerContext = commitTimer.time();
40     }
41
42     @Override
43     public void success() {
44         Preconditions.checkState(timerContext != null, "Call run before success");
45         timerContext.stop();
46
47         double newRateLimit = calculateNewRateLimit(commitTimer, actorContext.getDatastoreContext());
48
49         LOG.debug("Data Store {} commit rateLimit adjusted to {}", actorContext.getDataStoreType(), newRateLimit);
50
51         actorContext.setTxCreationLimit(newRateLimit);
52     }
53
54     @Override
55     public void failure() {
56         // This would mean we couldn't get a transaction completed in 30 seconds which is
57         // the default transaction commit timeout. Using the timeout information to figure out the rate limit is
58         // not going to be useful - so we leave it as it is
59     }
60
61     private static double calculateNewRateLimit(Timer commitTimer, DatastoreContext context) {
62         if(commitTimer == null) {
63             // This can happen in unit tests.
64             return 0;
65         }
66
67         Snapshot timerSnapshot = commitTimer.getSnapshot();
68         double newRateLimit = 0;
69
70         long commitTimeoutInSeconds = context.getShardTransactionCommitTimeoutInSeconds();
71         long commitTimeoutInNanos = TimeUnit.SECONDS.toNanos(commitTimeoutInSeconds);
72
73         // Find the time that it takes for transactions to get executed in every 10th percentile
74         // Compute the rate limit for that percentile and sum it up
75         for(int i=1;i<=10;i++){
76             // Get the amount of time transactions take in the i*10th percentile
77             double percentileTimeInNanos = timerSnapshot.getValue(i * 0.1D);
78
79             if(percentileTimeInNanos > 0) {
80                 // Figure out the rate limit for the i*10th percentile in nanos
81                 double percentileRateLimit = (commitTimeoutInNanos / percentileTimeInNanos);
82
83                 // Add the percentileRateLimit to the total rate limit
84                 newRateLimit += percentileRateLimit;
85             }
86         }
87
88         // Compute the rate limit per second
89         return newRateLimit/(commitTimeoutInSeconds*10);
90     }
91
92     public static void adjustRateLimitForUnusedTransaction(ActorContext actorContext) {
93         // Unused transactions in one data store can artificially limit the rate for other data stores
94         // if the first data store's rate is still at a lower initial rate since the front-end creates
95         // transactions in each data store up-front even though the client may not actually submit changes.
96         // So we may have to adjust the rate for data stores with unused transactions.
97
98         // First calculate the current rate for the data store. If it's 0 then there have been no
99         // actual transactions committed to the data store.
100
101         double newRateLimit = calculateNewRateLimit(actorContext.getOperationTimer(COMMIT),
102                 actorContext.getDatastoreContext());
103         if(newRateLimit == 0.0) {
104             // Since we have no rate data for unused Tx's data store, adjust to the rate from another
105             // data store that does have rate data.
106             for(String datastoreType: DatastoreContext.getGlobalDatastoreTypes()) {
107                 if(datastoreType.equals(actorContext.getDataStoreType())) {
108                     continue;
109                 }
110
111                 newRateLimit = calculateNewRateLimit(actorContext.getOperationTimer(datastoreType, COMMIT),
112                         actorContext.getDatastoreContext());
113                 if(newRateLimit > 0.0) {
114                     LOG.debug("On unused Tx - data Store {} commit rateLimit adjusted to {}",
115                             actorContext.getDataStoreType(), newRateLimit);
116
117                     actorContext.setTxCreationLimit(newRateLimit);
118                     break;
119                 }
120             }
121         }
122     }
123 }

©2013 OpenDaylight, A Linux Foundation Collaborative Project. All Rights Reserved.
OpenDaylight is a registered trademark of The OpenDaylight Project, Inc.
Linux Foundation and OpenDaylight are registered trademarks of the Linux Foundation.
Linux is a registered trademark of Linus Torvalds.