I use INCR and EXPIRE to implement rate limiting, e.g., 5 requests per minute:
if EXISTS counter
count = INCR counter
else
EXPIRE counter 60
count = INCR counter
if count > 5
print "Exceeded the limit"
However, 5 requests can be sent at the last second minute one and 5 more requests at the first second of minute two, i.e., 10 requests in two seconds.
How can this problem be avoided?
Update: I came up with this list implementation. Is this a good way to do it?
times = LLEN counter
if times < 5
LPUSH counter now()
else
time = LINDEX counter -1
if now() - time < 60
print "Exceeded the limit"
else
LPUSH counter now()
LTRIM counter 5
You could switch from "5 requests in the last minute" to "5 requests in minute x". By this it would be possible to do:
counter = current_time # for example 15:03
count = INCR counter
EXPIRE counter 60 # just to make sure redis doesn't store it forever
if count > 5
print "Exceeded the limit"
If you want to keep using "5 requests in the last minute", then you could do
counter = Time.now.to_i # this is Ruby and it returns the number of milliseconds since 1/1/1970
key = "counter:" + counter
INCR key
EXPIRE key 60
number_of_requests = KEYS "counter"*"
if number_of_requests > 5
print "Exceeded the limit"
If you have production constraints (especially performance), it is not advised to use the KEYS keyword. We could use sets instead:
counter = Time.now.to_i # this is Ruby and it returns the number of milliseconds since 1/1/1970
set = "my_set"
SADD set counter 1
members = SMEMBERS set
# remove all set members which are older than 1 minute
members {|member| SREM member if member[key] < (Time.now.to_i - 60000) }
if (SMEMBERS set).size > 5
print "Exceeded the limit"
This is all pseudo Ruby code, but should give you the idea.
The canonical way to do rate limiting is via the Leaky bucket algorithm. The downside of using a counter, is that a user can perform a bunch of request right after the counter is reset, i.e. 5 actions in the first second of the next minute for your case. The Leaky bucket algorithm solves this problem. Briefly, you can used ordered sets to store your "leaky bucket", using action time stamps as keys to fill it.
Check out this article for the exact implementation:
Better Rate Limiting With Redis Sorted Sets
UPDATE:
There is also another algorithm, which has some advantages compared to leaky bucket. It's called Generic Cell Rate Algorithm . Here's how it works at the higher level, as described in Rate Limiting, Cells, and GCRA:
GCRA works by tracking remaining limit through a time called the “theoretical arrival time” (TAT), which is seeded on the first request by adding a duration representing its cost to the current time. The cost is calculated as a multiplier of our “emission interval” (T), which is derived from the rate at which we want the bucket to refill. When any subsequent request comes in, we take the existing TAT, subtract a fixed buffer representing the limit’s total burst capacity from it (τ + T), and compare the result to the current time. This result represents the next time to allow a request. If it’s in the past, we allow the incoming request, and if it’s in the future, we don’t. After a successful request, a new TAT is calculated by adding T.
There is a redis module that implements this algorithm available on GitHub: https://github.com/brandur/redis-cell
This is an old question that was already answered, but here's an implementation I did taking some inspiration from here. I'm using ioredis for Node.js
Here is the rolling-window time limiter in all its asynchronous yet race-condition-free (I hope) glory:
var Ioredis = require('ioredis');
var redis = new Ioredis();
// Rolling window rate limiter
//
// key is a unique identifier for the process or function call being limited
// exp is the expiry in milliseconds
// maxnum is the number of function calls allowed before expiry
var redis_limiter_rolling = function(key, maxnum, exp, next) {
redis.multi([
['incr', 'limiter:num:' + key],
['time']
]).exec(function(err, results) {
if (err) {
next(err);
} else {
// unique incremented list number for this key
var listnum = results[0][1];
// current time
var tcur = (parseInt(results[1][1][0], 10) * 1000) + Math.floor(parseInt(results[1][1][1], 10) / 1000);
// absolute time of expiry
var texpiry = tcur - exp;
// get number of transacation in the last expiry time
var listkey = 'limiter:list:' + key;
redis.multi([
['zadd', listkey, tcur.toString(), listnum],
['zremrangebyscore', listkey, '-inf', texpiry.toString()],
['zcard', listkey]
]).exec(function(err, results) {
if (err) {
next(err);
} else {
// num is the number of calls in the last expiry time window
var num = parseInt(results[2][1], 10);
if (num <= maxnum) {
// does not reach limit
next(null, false, num, exp);
} else {
// limit surpassed
next(null, true, num, exp);
}
}
});
}
});
};
and here is a kind of lockout-style rate limiter:
// Lockout window rate limiter
//
// key is a unique identifier for the process or function call being limited
// exp is the expiry in milliseconds
// maxnum is the number of function calls allowed within expiry time
var util_limiter_lockout = function(key, maxnum, exp, next) {
// lockout rate limiter
var idkey = 'limiter:lock:' + key;
redis.incr(idkey, function(err, result) {
if (err) {
next(err);
} else {
if (result <= maxnum) {
// still within number of allowable calls
// - reset expiry and allow next function call
redis.expire(idkey, exp, function(err) {
if (err) {
next(err);
} else {
next(null, false, result);
}
});
} else {
// too many calls, user must wait for expiry of idkey
next(null, true, result);
}
}
});
};
Here's a gist of the functions. Let me know if you see any issues.
Note: The following code is a sample implementation in Java.
private final String COUNT = "count";
#Autowired
private StringRedisTemplate stringRedisTemplate;
private HashOperations hashOperations;
#PostConstruct
private void init() {
hashOperations = stringRedisTemplate.opsForHash();
}
#Override
public boolean isRequestAllowed(String key, long limit, long timeout, TimeUnit timeUnit) {
Boolean hasKey = stringRedisTemplate.hasKey(key);
if (hasKey) {
Long value = hashOperations.increment(key, COUNT, -1l);
return value > 0;
} else {
hashOperations.put(key, COUNT, String.valueOf(limit));
stringRedisTemplate.expire(key, timeout, timeUnit);
}
return true;
}
Here's my leaky bucket implementation of rate limiting, using Redis Lists.
Note: The following code is a sample implementation in php, you can implement it in your own language.
$list = $redis->lRange($key, 0, -1); // get whole list
$noOfRequests = count($list);
if ($noOfRequests > 5) {
$expired = 0;
foreach ($list as $timestamp) {
if ((time() - $timestamp) > 60) { // Time difference more than 1 min == expired
$expired++;
}
}
if ($expired > 0) {
$redis->lTrim($key, $expired, -1); // Remove expired requests
if (($noOfRequests - $expired) > 5) { // If still no of requests greater than 5, means fresh limit exceeded.
die("Request limit exceeded");
}
} else { // No expired == all fresh.
die("Request limit exceeded");
}
}
$redis->rPush($key, time()); // Add this request as a genuine one to the list, and proceed.
Your update is a very nice algorithm, although I a made couple of changes:
times = LLEN counter
if times < 5
LPUSH counter now()
else
time = LINDEX counter -1
if now() - time <= 60
print "Exceeded the limit"
else
LPUSH counter now()
RPOP counter
Similar to other Java answer but will less round trip to Redis:
#Autowired
private StringRedisTemplate stringRedisTemplate;
private HashOperations hashOperations;
#PostConstruct
private void init() {
hashOperations = stringRedisTemplate.opsForHash();
}
#Override
public boolean isRequestAllowed(String key, long limit, long timeout, TimeUnit timeUnit) {
Long value = hashOperations.increment(key, COUNT, 1l);
if (value == 1) {
stringRedisTemplate.expire(key, timeout, timeUnit);
}
return value > limit;
}
Here is an alternative approach. If the goal is to limit the number of requests to X requests per Y seconds with the timer starting when the first request is received, then you could create 2 keys for each user that you want to track: one for the time that the first request was received and another for the number of requests made.
key = "123"
key_count = "ct:#{key}"
key_timestamp = "ts:#{key}"
if (not redis[key_timestamp].nil?) && (not redis[key_count].nil?) && (redis[key_count].to_i > 3)
puts "limit reached"
else
if redis[key_timestamp].nil?
redis.multi do
redis.set(key_count, 1)
redis.set(key_timestamp, 1)
redis.expire(key_timestamp,30)
end
else
redis.incr(key_count)
end
puts redis[key_count].to_s + " : " + redis[key_timestamp].to_s + " : " + redis.ttl(key_timestamp).to_s
end
This is small enough that you might get away with not hashing it.
local f,k,a,b f=redis.call k=KEYS[1] a=f('incrby',k,ARGV[1]) b=f('pttl',k) if b<0 then f('pexpire',k,ARGV[2]) end return a
The parameters are:
KEYS[1] = key name, could be the action to rate limit for example
ARGV[1] = amount to increment, usually 1, but you could batch up per 10 or 100 millisecond intervals on the client
ARGV[2] = window, in milliseconds, to rate limit in
Returns: The new incremented value, which can then be compared to a value in your code to see if it's over the rate limit.
The ttl will not be set back to the base value with this method, it will continue to slide down until the key expires, at which point it will start over with ARGV[2] ttl on the next call.
Requests in Last interval / Sliding window
interval == Amount of time that number of requests(throughput) accepted
throughput == number of requests per interval
RequestTimeList == Each request time added to this list
// Remove older request entries
while (!RequestTimeList.isEmpty() && (now() - RequestTimeList.get(0)) > interval) {
RequestTimeList.remove(0)
}
if (RequestTimeList.length < throughput) {
RequestTimeList.add(now())
} else {
throw err;
}
Requests in Interval / Fixed window
I have tried with LIST, EXPIRE and PTTL
If tps is 5 per second, then
throughput = 5
rampup = 1000 (1000ms = 1sec)
interval = 200ms
local tpsKey = KEYS[1]
local throughput = tonumber(ARGV[1])
local rampUp = tonumber(ARGV[2])
-- Minimum interval to accept the next request.
local interval = rampUp / throughput
local currentTime = redis.call('PTTL', tpsKey)
-- -2 if the key does not exist, so set an year expiry
if currentTime == -2 then
currentTime = 31536000000 - interval
redis.call('SET', tpsKey, 31536000000, "PX", currentTime)
end
local previousTime = redis.call('GET', tpsKey)
if (previousTime - currentTime) >= interval then
redis.call('SET', tpsKey, currentTime, "PX", currentTime)
return true
else
redis.call('ECHO',"0. ERR - MAX PERMIT REACHED IN THIS INTERVAL")
return false
end
another way with List
local counter = KEYS[1]
local throughput = tonumber(ARGV[1])
local rampUp = tonumber(ARGV[2])
local interval = rampUp / throughput
local times = redis.call('LLEN', counter)
if times == 0 then
redis.call('LPUSH', counter, rampUp)
redis.call('PEXPIRE', counter, rampUp)
return true
elseif times < throughput then
local lastElemTTL = tonumber(redis.call('LINDEX', counter, 0))
local currentTTL = redis.call('PTTL', counter)
if (lastElemTTL-currentTTL) < interval then
return false
else
redis.call('LPUSH', counter, currentTTL)
return true
end
else
return false
end
Redis streams (introduced in redis 5.0, 2018) provide a nice way of implementing a sliding window api limiter. Here's my implementation in Python
r = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
#app.middleware("http")
async def rate_limit(request: Request, call_next):
request_time = time.time()
host = request.client.host
# Settings
window_seconds = 10
max_requests_in_window = 2
# Fetch the oldest element in the stream
# Returns a 0- or 1-element list like: [('1660835163482-0', {'': ''})]
oldest = r.xrange(name=host, min='-', max='+', count=1)
# if:
# - an oldest element exists AND
# - it's inside the time window AND
# - the stream is full
# deny the request
if len(oldest) > 0:
oldest_time = int(oldest[0][0].split('-')[0])/1000
if oldest_time >= request_time - window_seconds:
stream_size = r.xlen(name=host)
if stream_size >= max_requests_in_window:
return JSONResponse(status_code=403, content={'reason': oldest})
# Append this request to the stream and carry on
r.xadd(name=host, fields={'':''}, maxlen=max_requests_in_window, approximate=False)
# Carry on..
response = await call_next(request)
return response
Related
How could I generate steady CPU load in C#, lower than 100% for a certain time? I would also like to be able to change the load amount after a certain period of time. How do you recommend to generate usage spikes for a very short time?
First off, you have to understand that CPU usage is always an average over a certain time. At any given time, the CPU is either working or it is not. The CPU is never 40% working.
We can, however, simulate a 40% load over say a second by having the CPU work for 0.4 seconds and sleep 0.6 seconds. That gives an average utilization of 40% over that second.
Cutting it down to smaller than one second, say 100 millisecond chunks should give even more stable utilization.
The following method will take an argument that is desired utilization and then utilize a single CPU/core to that degree:
public static void ConsumeCPU(int percentage)
{
if (percentage < 0 || percentage > 100)
throw new ArgumentException("percentage");
Stopwatch watch = new Stopwatch();
watch.Start();
while (true)
{
// Make the loop go on for "percentage" milliseconds then sleep the
// remaining percentage milliseconds. So 40% utilization means work 40ms and sleep 60ms
if (watch.ElapsedMilliseconds > percentage)
{
Thread.Sleep(100 - percentage);
watch.Reset();
watch.Start();
}
}
}
I'm using a stopwatch here because it is more accurate than the the TickCount property, but you could likewise use that and use subtraction to check if you've run long enough.
Two things to keep in mind:
on multi core systems, you will have to spawn one thread for each core. Otherwise, you'll see only one CPU/core being exercised giving roughly "percentage/number-of-cores" utilization.
Thread.Sleep is not very accurate. It will never guarantee times exactly to the millisecond so you will see some variations in your results
To answer your second question, about changing the utilization after a certain time, I suggest you run this method on one or more threads (depending on number of cores) and then when you want to change utilization you just stop those threads and spawn new ones with the new percentage values. That way, you don't have to implement thread communication to change percentage of a running thread.
Just in add of the Isak response, I let here a simple implementation for multicore:
public static void CPUKill(object cpuUsage)
{
Parallel.For(0, 1, new Action<int>((int i) =>
{
Stopwatch watch = new Stopwatch();
watch.Start();
while (true)
{
if (watch.ElapsedMilliseconds > (int)cpuUsage)
{
Thread.Sleep(100 - (int)cpuUsage);
watch.Reset();
watch.Start();
}
}
}));
}
static void Main(string[] args)
{
int cpuUsage = 50;
int time = 10000;
List<Thread> threads = new List<Thread>();
for (int i = 0; i < Environment.ProcessorCount; i++)
{
Thread t = new Thread(new ParameterizedThreadStart(CPUKill));
t.Start(cpuUsage);
threads.Add(t);
}
Thread.Sleep(time);
foreach (var t in threads)
{
t.Abort();
}
}
For a uniform stressing: Isak Savo's answer with a slight tweak. The problem is interesting. In reality there are workloads that far exceed it in terms of wattage used, thermal output, lane saturation, etc. and perhaps the use of a loop as the workload is poor and almost unrealistic.
int percentage = 80;
for (int i = 0; i < Environment.ProcessorCount; i++)
{
(new Thread(() =>
{
Stopwatch watch = new Stopwatch();
watch.Start();
while (true)
{
// Make the loop go on for "percentage" milliseconds then sleep the
// remaining percentage milliseconds. So 40% utilization means work 40ms and sleep 60ms
if (watch.ElapsedMilliseconds > percentage)
{
Thread.Sleep(100 - percentage);
watch.Reset();
watch.Start();
}
}
})).Start();
}
Each time you have to set cpuUsageIncreaseby variable.
for example:
1- Cpu % increase by > cpuUsageIncreaseby % for one minute.
2- Go down to 0% for 20 seconds.
3- Goto step 1.
private void test()
{
int cpuUsageIncreaseby = 10;
while (true)
{
for (int i = 0; i < 4; i++)
{
//Console.WriteLine("am running ");
//DateTime start = DateTime.Now;
int cpuUsage = cpuUsageIncreaseby;
int time = 60000; // duration for cpu must increase for process...
List<Thread> threads = new List<Thread>();
for (int j = 0; j < Environment.ProcessorCount; j++)
{
Thread t = new Thread(new ParameterizedThreadStart(CPUKill));
t.Start(cpuUsage);
threads.Add(t);
}
Thread.Sleep(time);
foreach (var t in threads)
{
t.Abort();
}
//DateTime end = DateTime.Now;
//TimeSpan span = end.Subtract(start);
//Console.WriteLine("Time Difference (seconds): " + span.Seconds);
//Console.WriteLine("10 sec wait... for another.");
cpuUsageIncreaseby = cpuUsageIncreaseby + 10;
System.Threading.Thread.Sleep(20000);
}
}
}
I am using OptaPlanner to solve what is effectively the Traveling Salesman Problem with Time Windows (TSPTW). I have a working initial solution based on the OptaPlanner provided VRPTW example.
I am now trying to address my requirements that deviate from the standard TSPTW, which are:
I am trying to minimize the total time spent rather than the total distance traveled. Because of this, idle time counts against me.
In additional to the standard time windowed visits I also must support no-later-than (NLT) visits (i.e. don't visit after X time) and no-earlier-than (NET) visits (i.e don't visit before X time).
My current solution always sets the first visit's arrival time to that visit's start time. This has the following problems with respect to my requirements:
This can introduce unnecessary idle time that could be avoided if the visit was arrived at sometime later in its time window.
The behavior with NLT is problematic. If I define an NLT with the start time set to Long.MIN_VALUE (to represent that it is unbounded without resorting to nulls) then that is the time the NLT visit is arrived at (the same problem as #1). I tried addressing this by setting the start time to the NLT time. This resulted in arriving just in time for the NLT visit but overshooting the time windows of subsequent visits.
How should I address this/these problems? I suspect a solution will involve ArrivalTimeUpdatingVariableListener but I don't know what that solution should look like.
In case it's relevant, I've pasted in my current scoring rules below. One thing to note is that "distance" is really travel time. Also, for domain reasons, I am encouraging NLT and NET arrival times to be close to the cutoff time (end time for NLT, start time for NET).
import org.optaplanner.core.api.score.buildin.hardsoftlong.HardSoftLongScoreHolder;
global HardSoftLongScoreHolder scoreHolder;
// Hard Constraints
rule "ArrivalAfterWindowEnd"
when
Visit(arrivalTime > maxStartTime, $arrivalTime : arrivalTime, $maxStartTime : maxStartTime)
then
scoreHolder.addHardConstraintMatch(kcontext, $maxStartTime - $arrivalTime);
end
// Soft Constraints
rule "MinimizeDistanceToPreviousEvent"
when
Visit(previousRouteEvent != null, $distanceFromPreviousRouteEvent : distanceFromPreviousRouteEvent)
then
scoreHolder.addSoftConstraintMatch(kcontext, -$distanceFromPreviousRouteEvent);
end
rule "MinimizeDistanceFromLastEventToHome"
when
$visit : Visit(previousRouteEvent != null)
not Visit(previousRouteEvent == $visit)
$home : Home()
then
scoreHolder.addSoftConstraintMatch(kcontext, -$visit.getDistanceTo($home));
end
rule "MinimizeIdle"
when
Visit(scheduleType != ScheduleType.NLT, arrivalTime < minStartTime, $minStartTime : minStartTime, $arrivalTime : arrivalTime)
then
scoreHolder.addSoftConstraintMatch(kcontext, $arrivalTime - $minStartTime);
end
rule "PreferLatestNLT"
when
Visit(scheduleType == ScheduleType.NLT, arrivalTime < maxStartTime, $maxStartTime : maxStartTime, $arrivalTime : arrivalTime)
then
scoreHolder.addSoftConstraintMatch(kcontext, $arrivalTime - $maxStartTime);
end
rule "PreferEarliestNET"
when
Visit(scheduleType == ScheduleType.NET, arrivalTime > minStartTime, $minStartTime : minStartTime, $arrivalTime : arrivalTime)
then
scoreHolder.addSoftConstraintMatch(kcontext, $minStartTime - $arrivalTime);
end
To see an example that uses real road times instead of road distances: In the examples app, open Vehicle Routing, click button Import, load the file roaddistance/capacitated/belgium-road-time-n50-k10.vrp. Those times were calculated with GraphHopper.
To see an example that uses Time Windows, open the Vehicle Routing and quick open a dataset that is called cvrptw (tw stands for Time Windows). If you look at the academic spec (linked from docs chapter 3 IIRC) for CVRPTW, you'll see it already has a hard constraint "Do not arrive after time window closes" - so you'll see that one in score rules drl. As for arriving too early (and therefore losing the idle time): copy paste that hard constraint, make it a soft, make it use readyTime instead of dueTime and reverse it's comparison and penalty calculation. I actually originally implemented that (as it's the logical thing to have), but because I followed the academic spec (to compare with results of the academics) I had to remove it.
I was able to solve my problem by modifying ArrivalTimeUpdatingVariableListener's updateArrivalTime method to reach backwards and (attempt to) shift the previous arrival time. Additionally, I introduced a getPreferredStartTime() method to support NLT events defaulting to as late as possible. Finally, just for code cleanliness, I moved the updateArrivalTime method from ArrivalTimeUpdatingVariableListener into the Visit class.
Here is the relevant code from the Visit class:
public long getPreferredStartTime()
{
switch(scheduleType)
{
case NLT:
return getMaxStartTime();
default:
return getMinStartTime();
}
}
public Long getStartTime()
{
Long arrivalTime = getArrivalTime();
if (arrivalTime == null)
{
return null;
}
switch(scheduleType)
{
case NLT:
return arrivalTime;
default:
return Math.max(arrivalTime, getMinStartTime());
}
}
public Long getEndTime()
{
Long startTime = getStartTime();
if (startTime == null)
{
return null;
}
return startTime + duration;
}
public void updateArrivalTime(ScoreDirector scoreDirector)
{
if(previousRouteEvent instanceof Visit)
{
updateArrivalTime(scoreDirector, (Visit)previousRouteEvent);
return;
}
long arrivalTime = getPreferredStartTime();
if(Utilities.equal(this.arrivalTime, arrivalTime))
{
return;
}
setArrivalTime(scoreDirector, arrivalTime);
}
private void updateArrivalTime(ScoreDirector scoreDirector, Visit previousVisit)
{
long departureTime = previousVisit.getEndTime();
long arrivalTime = departureTime + getDistanceFromPreviousRouteEvent();
if(Utilities.equal(this.arrivalTime, arrivalTime))
{
return;
}
if(arrivalTime > maxStartTime)
{
if(previousVisit.shiftTimeLeft(scoreDirector, arrivalTime - maxStartTime))
{
return;
}
}
else if(arrivalTime < minStartTime)
{
if(previousVisit.shiftTimeRight(scoreDirector, minStartTime - arrivalTime))
{
return;
}
}
setArrivalTime(scoreDirector, arrivalTime);
}
/**
* Set the arrival time and propagate the change to any following entities.
*/
private void setArrivalTime(ScoreDirector scoreDirector, long arrivalTime)
{
scoreDirector.beforeVariableChanged(this, "arrivalTime");
this.arrivalTime = arrivalTime;
scoreDirector.afterVariableChanged(this, "arrivalTime");
Visit nextEntity = getNextVisit();
if(nextEntity != null)
{
nextEntity.updateArrivalTime(scoreDirector, this);
}
}
/**
* Attempt to shift the arrival time backward by the specified amount.
* #param requested The amount of time that should be subtracted from the arrival time.
* #return Returns true if the arrival time was changed.
*/
private boolean shiftTimeLeft(ScoreDirector scoreDirector, long requested)
{
long available = arrivalTime - minStartTime;
if(available <= 0)
{
return false;
}
requested = Math.min(requested, available);
if(previousRouteEvent instanceof Visit)
{
//Arrival time is inflexible as this is not the first event. Forward to previous event.
return ((Visit)previousRouteEvent).shiftTimeLeft(scoreDirector, requested);
}
setArrivalTime(scoreDirector, arrivalTime - requested);
return true;
}
/**
* Attempt to shift the arrival time forward by the specified amount.
* #param requested The amount of time that should be added to the arrival time.
* #return Returns true if the arrival time was changed.
*/
private boolean shiftTimeRight(ScoreDirector scoreDirector, long requested)
{
long available = maxStartTime - arrivalTime;
if(available <= 0)
{
return false;
}
requested = Math.min(requested, available);
if(previousRouteEvent instanceof Visit)
{
//Arrival time is inflexible as this is not the first event. Forward to previous event.
//Note, we could start later anyways but that won't decrease idle time, which is the purpose of shifting right
return ((Visit)previousRouteEvent).shiftTimeRight(scoreDirector, requested);
}
setArrivalTime(scoreDirector, arrivalTime + requested);
return false;
}
Using dtrace on Solaris, I am able to accumulate on-cpu time for a given process (or execname) for the interval from start until control-C with the following script:
!/usr/sbin/dtrace -qs
dtrace:::BEGIN {
total = 0;
}
sched:::on-cpu
/execname == $$1/
{
self->start = vtimestamp;
}
sched:::off-cpu
/self->start/
{
this->time = vtimestamp - self->start;
total += this->time;
self->start = 0;
}
dtrace:::END {
printf("Total Time on CPU: %d us\n",total/1000);
}
(Accumulated time has a fine-grain granularity allowing nano/microsecond accumulation.)
Over the same timeframe, I would like to accumulate all or many processes in an array and report on all accumulated cpu time at break (^C).
What is the best way to do this?
Okay, with a bit more work I've solved my problem.
Here is the way to get microseconds for all processes (but display per process) over the interval.
#!/usr/sbin/dtrace -qs
dtrace:::BEGIN {
total = 0;
starttimestamp=timestamp;
printf("Starting...\n");
}
sched:::on-cpu
/pid!=0/
{
self->start = vtimestamp;
}
sched:::off-cpu
/self->start && pid!=0/
{
this->time = vtimestamp - self->start;
total += this->time;
#proctime[pid,uid,execname,curpsinfo->pr_psargs] = sum( this->time/1000 );
self->start = 0;
}
dtrace:::END {
printf("Elapsed time %d usec\n",(timestamp-starttimestamp)/1000);
printf("Total Time on CPU: %d us\n",total/1000);
printa(#proctime);
}
#using <mscorlib.dll>
#using <System.dll>
using namespace System;
using namespace System::Text;
using namespace System::IO;
using namespace System::Net;
using namespace System::Net::Sockets;
using namespace System::Collections;
Errors: IntelliSense: "#using" requires C++/CLI to be enabled....
how to fix this prob!?
Your project settings are wrong. Specifically Configuration Properties, General, Common Language Runtime support.
Fall in the pit of success by starting your project by picking one of the project templates in the CLR node.
Choose Project -> Properties from the menu bar. In the Project properties window, under Configuration Properties -> General, make sure that Common Language Runtime Support is set to Common Language Runtime Support (/clr)
In VS2019 it the steps would be :
1/ Right click on the project
2/ Project
3/ Properties
4/ Configuration Properties
5/ Advanced
6/ Common Language Runtime Support change it to Common Language Runtime Support(/clr)
Enable it in your project settings (right click on the projet -> settings) the first tab should provide the option.
The MSDN has a nice example for testing the difference in performance, Parse vs tryParse:
Stopwatch Example
#include <stdio.h>
#using <System.dll>
using namespace System;
using namespace System::Diagnostics;
void DisplayTimerProperties()
{
// Display the timer frequency and resolution.
if (Stopwatch::IsHighResolution)
{
Console::WriteLine("Operations timed using the system's high-resolution performance counter.");
}
else
{
Console::WriteLine("Operations timed using the DateTime class.");
}
Int64 frequency = Stopwatch::Frequency;
Console::WriteLine(" Timer frequency in ticks per second = {0}", frequency);
Int64 nanosecPerTick = (1000L * 1000L * 1000L) / frequency;
Console::WriteLine(" Timer is accurate within {0} nanoseconds", nanosecPerTick);
}
void TimeOperations()
{
Int64 nanosecPerTick = (1000L * 1000L * 1000L) / Stopwatch::Frequency;
const long numIterations = 10000;
// Define the operation title names.
array<String^>^operationNames = { "Operation: Int32.Parse(\"0\")","Operation: Int32.TryParse(\"0\")","Operation: Int32.Parse(\"a\")","Operation: Int32.TryParse(\"a\")" };
// Time four different implementations for parsing
// an integer from a string.
for (int operation = 0; operation <= 3; operation++)
{
// Define variables for operation statistics.
Int64 numTicks = 0;
Int64 numRollovers = 0;
Int64 maxTicks = 0;
Int64 minTicks = Int64::MaxValue;
int indexFastest = -1;
int indexSlowest = -1;
Int64 milliSec = 0;
Stopwatch ^ time10kOperations = Stopwatch::StartNew();
// Run the current operation 10001 times.
// The first execution time will be tossed
// out, since it can skew the average time.
for (int i = 0; i <= numIterations; i++)
{
Int64 ticksThisTime = 0;
int inputNum;
Stopwatch ^ timePerParse;
switch (operation)
{
case 0:
// Parse a valid integer using
// a try-catch statement.
// Start a new stopwatch timer.
timePerParse = Stopwatch::StartNew();
try
{
inputNum = Int32::Parse("0");
}
catch (FormatException^)
{
inputNum = 0;
}
// Stop the timer, and save the
// elapsed ticks for the operation.
timePerParse->Stop();
ticksThisTime = timePerParse->ElapsedTicks;
break;
case 1:
// Parse a valid integer using
// the TryParse statement.
// Start a new stopwatch timer.
timePerParse = Stopwatch::StartNew();
if (!Int32::TryParse("0", inputNum))
{
inputNum = 0;
}
// Stop the timer, and save the
// elapsed ticks for the operation.
timePerParse->Stop();
ticksThisTime = timePerParse->ElapsedTicks;
break;
case 2:
// Parse an invalid value using
// a try-catch statement.
// Start a new stopwatch timer.
timePerParse = Stopwatch::StartNew();
try
{
inputNum = Int32::Parse("a");
}
catch (FormatException^)
{
inputNum = 0;
}
// Stop the timer, and save the
// elapsed ticks for the operation.
timePerParse->Stop();
ticksThisTime = timePerParse->ElapsedTicks;
break;
case 3:
// Parse an invalid value using
// the TryParse statement.
// Start a new stopwatch timer.
timePerParse = Stopwatch::StartNew();
if (!Int32::TryParse("a", inputNum))
{
inputNum = 0;
}
// Stop the timer, and save the
// elapsed ticks for the operation.
timePerParse->Stop();
ticksThisTime = timePerParse->ElapsedTicks;
break;
default:
break;
}
// Skip over the time for the first operation,
// just in case it caused a one-time
// performance hit.
if (i == 0)
{
time10kOperations->Reset();
time10kOperations->Start();
}
else
{
// Update operation statistics
// for iterations 1-10001.
if (maxTicks < ticksThisTime)
{
indexSlowest = i;
maxTicks = ticksThisTime;
}
if (minTicks > ticksThisTime)
{
indexFastest = i;
minTicks = ticksThisTime;
}
numTicks += ticksThisTime;
if (numTicks < ticksThisTime)
{
// Keep track of rollovers.
numRollovers++;
}
}
}
// Display the statistics for 10000 iterations.
time10kOperations->Stop();
milliSec = time10kOperations->ElapsedMilliseconds;
Console::WriteLine();
Console::WriteLine("{0} Summary:", operationNames[operation]);
Console::WriteLine(" Slowest time: #{0}/{1} = {2} ticks", indexSlowest, numIterations, maxTicks);
Console::WriteLine(" Fastest time: #{0}/{1} = {2} ticks", indexFastest, numIterations, minTicks);
Console::WriteLine(" Average time: {0} ticks = {1} nanoseconds", numTicks / numIterations, (numTicks * nanosecPerTick) / numIterations);
Console::WriteLine(" Total time looping through {0} operations: {1} milliseconds", numIterations, milliSec);
}
}
int main()
{
DisplayTimerProperties();
Console::WriteLine();
Console::WriteLine("Press the Enter key to begin:");
Console::ReadLine();
Console::WriteLine();
TimeOperations();
getchar();
}
//Operations timed using the system's high-resolution performance counter.
//Timer frequency in ticks per second = 3319338
//Timer is accurate within 301 nanoseconds
//
//Press the Enter key to begin :
//
//
//
//Operation : Int32.Parse("0") Summary :
// Slowest time : #4483 / 10000 = 95 ticks
// Fastest time : #3 / 10000 = 0 ticks
// Average time : 0 ticks = 99 nanoseconds
// Total time looping through 10000 operations : 1 milliseconds
//
// Operation : Int32.TryParse("0") Summary :
// Slowest time : #7720 / 10000 = 187 ticks
// Fastest time : #1 / 10000 = 0 ticks
// Average time : 0 ticks = 109 nanoseconds
// Total time looping through 10000 operations : 1 milliseconds
//
// Operation : Int32.Parse("a") Summary :
// Slowest time : #3701 / 10000 = 2388 ticks
// Fastest time : #2698 / 10000 = 102 ticks
// Average time : 116 ticks = 35109 nanoseconds
// Total time looping through 10000 operations : 352 milliseconds
//
// Operation : Int32.TryParse("a") Summary :
// Slowest time : #8593 / 10000 = 23 ticks
// Fastest time : #1 / 10000 = 0 ticks
// Average time : 0 ticks = 88 nanoseconds
// Total time looping through 10000 operations : 1 milliseconds
If you are using Visual Studio, you might have to do some installations pre-hand. To install those, open the Visual Studio Installer from the Windows Start menu. Make sure that the Desktop development with C++ tile is checked, and in the Optional components section, also check C++/CLI Support.
I'd like to create 9-digit numeric ids that are unique across machines. I'm currently using a database sequence for this, but am wondering if it could be done without one. The sequences will be used for X12 EDI transactions, so they don't have to be unique forever. Maybe even only unique for 24 hours.
My only idea:
Each server has a 2 digit server identifier.
Each server maintains a file that essentially keeps track of a local sequence.
id = + <7 digit sequence which wraps>
My biggest problem with this is what to do if the hard-drive fails. I wouldn't know where it left off.
All of my other ideas essentially end up re-creating a centralized database sequence.
Any thoughts?
The Following
{XX}{dd}{HHmm}{N}
Where {XX} is the machine number {dd} is the day of the month {HHmm} current time (24hr) and {N} a sequential number.
A hd crash will take more than a minute so starting at 0 again is not a problem.
You can also replace {dd} with {ss} for seconds, depending on requirements. Uniqueness period vs. requests per minute.
If HD fails you can just set new and unused 2 digit server identifier and be sure that the number is unique (for 24 hours at least)
How about generating GUIDs (ensures uniqueness) and then using some sort of hash function to turn the GUID into a 9-digit number?
Just off the top of my head...
Use a variation on:
md5(uniqid(rand(), true));
Just a thought.
In my recent project I also come across this requirement, to generate N digit long sequence number without any database.
This is actually a good Interview question, because there are consideration on performance and software crash recovery. Further Reading if interested.
The following code has these features:
Prefix each sequence with a prefix.
Sequence cache like Oracle Sequence.
Most importantly, there is recovery logic to resume sequence from software crash.
Complete implementation attached:
import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.lang.StringUtils;
/**
* This is a customized Sequence Generator which simulates Oracle DB Sequence Generator. However the master sequence
* is stored locally in the file as there is no access to Oracle database. The output format is "prefix" + number.
* <p>
* <u><b>Sample output:</u></b><br>
* 1. FixLengthIDSequence(null,null,15,0,99,0) will generate 15, 16, ... 99, 00<br>
* 2. FixLengthIDSequence(null,"K",1,1,99,0) will generate K01, K02, ... K99, K01<br>
* 3. FixLengthIDSequence(null,"SG",100,2,9999,100) will generate SG0100, SG0101, ... SG8057, (in case server crashes, the new init value will start from last cache value+1) SG8101, ... SG9999, SG0002<br>
*/
public final class FixLengthIDSequence {
private static String FNAME;
private static String PREFIX;
private static AtomicLong SEQ_ID;
private static long MINVALUE;
private static long MAXVALUE;
private static long CACHEVALUE;
// some internal working values.
private int iMaxLength; // max numeric length excluding prefix, for left padding zeros.
private long lNextSnapshot; // to keep track of when to update sequence value to file.
private static boolean bInit = false; // to enable ShutdownHook routine after program has properly initialized
static {
// Inspiration from http://stackoverflow.com/questions/22416826/sequence-generator-in-java-for-unique-id#35697336.
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
if (bInit) { // Without this, saveToLocal may hit NullPointerException.
saveToLocal(SEQ_ID.longValue());
}
}));
}
/**
* This POJO style constructor should be initialized via Spring Singleton. Otherwise, rewrite this constructor into Singleton design pattern.
*
* #param sFilename This is the absolute file path to store the sequence number. To reset the sequence, this file needs to be removed manually.
* #param prefix The hard-coded identifier.
* #param initvalue
* #param minvalue
* #param maxvalue
* #param cache
* #throws Exception
*/
public FixLengthIDSequence(String sFilename, String prefix, long initvalue, long minvalue, long maxvalue, int cache) throws Exception {
bInit = false;
FNAME = (sFilename==null)?"C:\\Temp\\sequence.txt":sFilename;
PREFIX = (prefix==null)?"":prefix;
SEQ_ID = new AtomicLong(initvalue);
MINVALUE = minvalue;
MAXVALUE = maxvalue; iMaxLength = Long.toString(MAXVALUE).length();
CACHEVALUE = (cache <= 0)?1:cache; lNextSnapshot = roundUpNumberByMultipleValue(initvalue, cache); // Internal cache is always 1, equals no cache.
// If sequence file exists and valid, restore the saved sequence.
java.io.File f = new java.io.File(FNAME);
if (f.exists()) {
String[] saSavedSequence = loadToString().split(",");
if (saSavedSequence.length != 6) {
throw new Exception("Local Sequence file is not valid");
}
PREFIX = saSavedSequence[0];
//SEQ_ID = new AtomicLong(Long.parseLong(saSavedSequence[1])); // savedInitValue
MINVALUE = Long.parseLong(saSavedSequence[2]);
MAXVALUE = Long.parseLong(saSavedSequence[3]); iMaxLength = Long.toString(MAXVALUE).length();
CACHEVALUE = Long.parseLong(saSavedSequence[4]);
lNextSnapshot = Long.parseLong(saSavedSequence[5]);
// For sequence number recovery
// The rule to determine to continue using SEQ_ID or lNextSnapshot as subsequent sequence number:
// If savedInitValue = savedSnapshot, it was saved by ShutdownHook -> use SEQ_ID.
// Else if saveInitValue < savedSnapshot, it was saved by periodic Snapshot -> use lNextSnapshot+1.
if (saSavedSequence[1].equals(saSavedSequence[5])) {
long previousSEQ = Long.parseLong(saSavedSequence[1]);
SEQ_ID = new AtomicLong(previousSEQ);
lNextSnapshot = roundUpNumberByMultipleValue(previousSEQ,CACHEVALUE);
} else {
SEQ_ID = new AtomicLong(lNextSnapshot+1); // SEQ_ID starts fresh from lNextSnapshot+!.
lNextSnapshot = roundUpNumberByMultipleValue(SEQ_ID.longValue(),CACHEVALUE);
}
}
// Catch invalid values.
if (minvalue < 0) {
throw new Exception("MINVALUE cannot be less than 0");
}
if (maxvalue < 0) {
throw new Exception("MAXVALUE cannot be less than 0");
}
if (minvalue >= maxvalue) {
throw new Exception("MINVALUE cannot be greater than MAXVALUE");
}
if (cache >= maxvalue) {
throw new Exception("CACHE value cannot be greater than MAXVALUE");
}
// Save the next Snapshot.
saveToLocal(lNextSnapshot);
bInit = true;
}
/**
* Equivalent to Oracle Sequence nextval.
* #return String because Next Value is usually left padded with zeros, e.g. "00001".
*/
public String nextVal() {
if (SEQ_ID.longValue() > MAXVALUE) {
SEQ_ID.set(MINVALUE);
lNextSnapshot = roundUpNumberByMultipleValue(MINVALUE,CACHEVALUE);
}
if (SEQ_ID.longValue() > lNextSnapshot) {
lNextSnapshot = roundUpNumberByMultipleValue(lNextSnapshot,CACHEVALUE);
saveToLocal(lNextSnapshot);
}
return PREFIX.concat(StringUtils.leftPad(Long.toString(SEQ_ID.getAndIncrement()),iMaxLength,"0"));
}
/**
* Store sequence value into the local file. This routine is called either by Snapshot or ShutdownHook routines.<br>
* If called by Snapshot, currentCount == Snapshot.<br>
* If called by ShutdownHook, currentCount == current SEQ_ID.
* #param currentCount - This value is inserted by either Snapshot or ShutdownHook routines.
*/
private static void saveToLocal (long currentCount) {
try (java.io.Writer w = new java.io.BufferedWriter(new java.io.OutputStreamWriter(new java.io.FileOutputStream(FNAME), "utf-8"))) {
w.write(PREFIX + "," + SEQ_ID.longValue() + "," + MINVALUE + "," + MAXVALUE + "," + CACHEVALUE + "," + currentCount);
w.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Load the sequence file content into String.
* #return
*/
private String loadToString() {
try {
return new String(java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(FNAME)));
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
/**
* Utility method to round up num to next multiple value. This method is used to calculate the next cache value.
* <p>
* (Reference: http://stackoverflow.com/questions/18407634/rounding-up-to-the-nearest-hundred)
* <p>
* <u><b>Sample output:</b></u>
* <pre>
* System.out.println(roundUpNumberByMultipleValue(9,10)); = 10
* System.out.println(roundUpNumberByMultipleValue(10,10)); = 20
* System.out.println(roundUpNumberByMultipleValue(19,10)); = 20
* System.out.println(roundUpNumberByMultipleValue(100,10)); = 110
* System.out.println(roundUpNumberByMultipleValue(109,10)); = 110
* System.out.println(roundUpNumberByMultipleValue(110,10)); = 120
* System.out.println(roundUpNumberByMultipleValue(119,10)); = 120
* </pre>
*
* #param num Value must be greater and equals to positive integer 1.
* #param multiple Value must be greater and equals to positive integer 1.
* #return
*/
private long roundUpNumberByMultipleValue(long num, long multiple) {
if (num<=0) num=1;
if (multiple<=0) multiple=1;
if (num % multiple != 0) {
long division = (long) ((num / multiple) + 1);
return division * multiple;
} else {
return num + multiple;
}
}
/**
* Main method for testing purpose.
* #param args
*/
public static void main(String[] args) throws Exception {
//FixLengthIDSequence(Filename, prefix, initvalue, minvalue, maxvalue, cache)
FixLengthIDSequence seq = new FixLengthIDSequence(null,"H",50,1,999,10);
for (int i=0; i<12; i++) {
System.out.println(seq.nextVal());
Thread.sleep(1000);
//if (i==8) { System.exit(0); }
}
}
}
To test the code, let the sequence run normally. You can press Ctrl+C to simulate the server crash. The next sequence number will continue from NextSnapshot+1.
Cold you use the first 9 digits of some other source of unique data like:
a random number
System Time
Uptime
Having thaught about it for two seconds, none of those are unique on there own but you could use them as seed values for hash functions as was suggested in another answer.