Coding it my way2024-11-01T09:34:22+05:30https://madhur.co.in/blog/Madhur Ahuja[email protected]Resize disk in Proxmox VM2024-11-01T00:00:00+05:30id:/blog/2024/11/01/resize-disks-proxmox<p>I needed to resize a disk in my Proxmox VM. Specifically, I was working with a Debian 12 (Bookworm) VM created through <a href="https://tteck.github.io/Proxmox/#debian-12-vm">Proxmox VE Helper Scripts</a>. These VMs come with a default size of 2 GB, even when using the advanced installation option. Unfortunately, there’s no way to select the disk size during the initial installation process.</p>
<h3 id="pre-requisites">Pre-Requisites</h3>
<p>First, install the required tools:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt <span class="nb">install </span>parted fdisk
</code></pre></div></div>
<h3 id="steps-to-resize-the-disk">Steps to Resize the disk</h3>
<ul>
<li>
<p>Resize the disk through Proxmox GUI.</p>
</li>
<li>
<p>Login to the VM and run <code class="language-plaintext highlighter-rouge">fdisk -l</code> . You should see warnings similar to:</p>
</li>
</ul>
<blockquote>
<p>GPT PMBR size mismatch (167772159 != 314572799) will be corrected by write.
The backup GPT table is not on the end of the device. This problem will be corrected by write.</p>
</blockquote>
<ul>
<li>Use parted to fix the partition table:</li>
</ul>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>parted /dev/sda
print
</code></pre></div></div>
<p>You’ll see a warning:</p>
<blockquote>
<p>Warning: Not all of the space available to /dev/sda appears to be used, you can fix the GPT to use all of the space (an extra 146800640 blocks) or continue with the current setting?</p>
</blockquote>
<ul>
<li>Fix the partition table</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Fix/Ignore? F
</code></pre></div></div>
<ul>
<li>Resize the partition:</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(parted) resizepart 1
Warning: Partition /dev/sda1 is being used. Are you sure you want to continue?
Yes/No? y
End? [85.9GB]? 100%
quit
</code></pre></div></div>
<ul>
<li>Apply the changes to the filesystem:</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>resize2fs /dev/sda1
</code></pre></div></div>
<p>Note: This method works only for ext4 non-LVM filesystems.</p>
s3cmd Fast way to copy s3 data2024-10-07T00:00:00+05:30id:/blog/2024/10/07/s5cmd-fastest-way-to-copy-s3-data<p>If you are looking to copy huge amount of data to/from <a href="https://aws.amazon.com/s3/">AWS S3</a></p>
<p>Look no further than <a href="https://github.com/peak/s5cmd">s5cmd</a></p>
<p>Download the entire s3 bucket to local</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>s5cmd cp s3://bucket/ .
</code></pre></div></div>
<p>I was able to download almost 10 GB folder in couple of minutes, which is around 83MBps ~ 10 MB/s</p>
<p>Whereas <code class="language-plaintext highlighter-rouge">aws s3 cp --recursive s3://bucket/ .</code> was giving me around 500 KB/s</p>
_class attribute in Spring data MongoDB_2024-10-05T00:00:00+05:30id:/blog/2024/10/05/class-attribute-spring-data-mongodb<p>Spring data MongoDB adds a <code class="language-plaintext highlighter-rouge">_class</code> attribute to every document written to <a href="https://www.mongodb.com/">MongoDB</a></p>
<p>If we want to prvent adding Spring Data MongoDB from adding <code class="language-plaintext highlighter-rouge">_class</code> attribute, we can configure this behaviour through:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">MongoConfig</span> <span class="kd">extends</span> <span class="nc">AbstractMongoClientConfiguration</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">boolean</span> <span class="nf">autoIndexCreation</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Bean</span>
<span class="kd">public</span> <span class="nc">MappingMongoConverter</span> <span class="nf">mappingMongoConverter</span><span class="o">(</span><span class="nc">MongoDatabaseFactory</span> <span class="n">databaseFactory</span><span class="o">,</span> <span class="nc">MongoMappingContext</span> <span class="n">mappingContext</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">DbRefResolver</span> <span class="n">dbRefResolver</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DefaultDbRefResolver</span><span class="o">(</span><span class="n">databaseFactory</span><span class="o">);</span>
<span class="nc">MappingMongoConverter</span> <span class="n">converter</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">MappingMongoConverter</span><span class="o">(</span><span class="n">dbRefResolver</span><span class="o">,</span> <span class="n">mappingContext</span><span class="o">);</span>
<span class="n">converter</span><span class="o">.</span><span class="na">setTypeMapper</span><span class="o">(</span><span class="k">new</span> <span class="nc">DefaultMongoTypeMapper</span><span class="o">(</span><span class="kc">null</span><span class="o">));</span>
<span class="k">return</span> <span class="n">converter</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
Create local kubernetes cluster using Kind2024-09-01T00:00:00+05:30id:/blog/2024/09/01/create-local-k8s-cluster-kind<p>If you want to quickly spin up a local development environment of kubernets cluster, Kubernetes in docker aka <a href="https://kind.sigs.k8s.io/">Kind</a> is the way to go</p>
<p>Kind runs the kubernetes inside a docker container as opposed to <a href="https://minikube.sigs.k8s.io/docs/">Minikube</a> which used to run kubernetes inside a VM</p>
<p>Running Kind on linux requires to create a cluster configuration file <code class="language-plaintext highlighter-rouge">cluster.yaml</code></p>
<p>Here is the sample configuration which would work for most of the cases:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">kind</span><span class="pi">:</span> <span class="s">Cluster</span>
<span class="na">apiVersion</span><span class="pi">:</span> <span class="s">kind.x-k8s.io/v1alpha4</span>
<span class="na">nodes</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">role</span><span class="pi">:</span> <span class="s">control-plane</span>
<span class="na">kubeadmConfigPatches</span><span class="pi">:</span>
<span class="pi">-</span> <span class="pi">|</span>
<span class="s">kind: InitConfiguration</span>
<span class="s">nodeRegistration:</span>
<span class="s">kubeletExtraArgs:</span>
<span class="s">node-labels: "ingress-ready=true"</span>
<span class="s">extraPortMappings:</span>
<span class="s">- containerPort: 80</span>
<span class="s">hostPort: 80</span>
<span class="s">protocol: TCP</span>
<span class="s">- containerPort: 443</span>
<span class="s">hostPort: 443</span>
<span class="s">protocol: TCP</span>
</code></pre></div></div>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kind create cluster <span class="nt">--config</span><span class="o">=</span>cluster.yaml
</code></pre></div></div>
<p>Upon creating the cluster, <code class="language-plaintext highlighter-rouge">kubectl</code> context is automatically set to point to <code class="language-plaintext highlighter-rouge">kind</code> cluster.</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> kubectl cluster-info <span class="nt">--context</span> kind-kind
Kubernetes control plane is running at https://127.0.0.1:46251
CoreDNS is running at https://127.0.0.1:46251/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use <span class="s1">'kubectl cluster-info dump'</span><span class="nb">.</span>
</code></pre></div></div>
Load testing Websocket servers using Locust2024-08-04T00:00:00+05:30id:/blog/2024/08/04/load-testing-websocket-servers-locust<p>The entire code of this article is present on this <a href="https://github.com/madhur/load-test-websocket-locust">github repository</a></p>
<p>Load testing WebSocket servers is crucial for ensuring their performance and scalability.</p>
<p>This article will guide you through using Locust, a popular open-source load testing tool, to test your Java WebSocket server.</p>
<p>We will use Jetty WebSocket in Java to run our websocket server. Jetty WebSocket is a Java library and part of the larger Jetty project, which is a popular open-source web server and servlet container.</p>
<p>The only message we will be handling is heartbeat requests for simplicity.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">private</span> <span class="kt">boolean</span> <span class="nf">start</span><span class="o">(</span><span class="nc">String</span> <span class="n">host</span><span class="o">,</span> <span class="kt">int</span> <span class="n">port</span><span class="o">)</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="nc">ServerConnector</span> <span class="n">socketConnector</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ServerConnector</span><span class="o">(</span><span class="n">server</span><span class="o">);</span>
<span class="n">socketConnector</span><span class="o">.</span><span class="na">setPort</span><span class="o">(</span><span class="n">port</span><span class="o">);</span>
<span class="n">socketConnector</span><span class="o">.</span><span class="na">setHost</span><span class="o">(</span><span class="n">host</span><span class="o">);</span>
<span class="n">server</span><span class="o">.</span><span class="na">setConnectors</span><span class="o">(</span><span class="k">new</span> <span class="nc">Connector</span><span class="o">[]</span> <span class="o">{</span> <span class="n">socketConnector</span> <span class="o">});</span>
<span class="n">disableServerVersionHeader</span><span class="o">();</span>
<span class="nc">WebSocketHandler</span> <span class="n">webSocketHandler</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">WebSocketHandler</span><span class="o">()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">configure</span><span class="o">(</span><span class="nc">WebSocketServletFactory</span> <span class="n">webSocketServletFactory</span><span class="o">)</span> <span class="o">{</span>
<span class="n">webSocketServletFactory</span><span class="o">.</span><span class="na">register</span><span class="o">(</span><span class="nc">WebSocketChannelHandler</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="n">webSocketServletFactory</span><span class="o">.</span><span class="na">getExtensionFactory</span><span class="o">().</span><span class="na">unregister</span><span class="o">(</span><span class="s">"permessage-deflate"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">};</span>
<span class="n">server</span><span class="o">.</span><span class="na">setHandler</span><span class="o">(</span><span class="n">webSocketHandler</span><span class="o">);</span>
<span class="n">server</span><span class="o">.</span><span class="na">start</span><span class="o">();</span>
<span class="n">isRunning</span> <span class="o">=</span> <span class="n">server</span><span class="o">.</span><span class="na">isStarted</span><span class="o">();</span>
<span class="n">logger</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Websocket Jetty server started @- {}/{}"</span><span class="o">,</span> <span class="n">host</span><span class="o">,</span> <span class="n">port</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">logger</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"Unable to start the server on {}/{}"</span><span class="o">,</span> <span class="n">host</span><span class="o">,</span> <span class="n">port</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
<span class="n">isRunning</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">isRunning</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Here is a simple handler which handles heartbeat requests</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@OnWebSocketMessage</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">onMessage</span><span class="o">(</span><span class="nc">Session</span> <span class="n">session</span><span class="o">,</span> <span class="nc">String</span> <span class="n">message</span><span class="o">)</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">logger</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"WS message received : {}"</span><span class="o">,</span> <span class="n">message</span><span class="o">);</span>
<span class="n">processMessage</span><span class="o">(</span><span class="n">session</span><span class="o">,</span> <span class="n">message</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">logger</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"Exception in onMessage for session {} message {} due to {}"</span><span class="o">,</span> <span class="n">session</span><span class="o">,</span> <span class="n">message</span><span class="o">,</span>
<span class="n">e</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">processMessage</span><span class="o">(</span><span class="nc">Session</span> <span class="n">session</span><span class="o">,</span> <span class="nc">String</span> <span class="n">message</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">JSONObject</span> <span class="n">json</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">JSONObject</span><span class="o">(</span><span class="n">message</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">messageType</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="na">optString</span><span class="o">(</span><span class="s">"type"</span><span class="o">).</span><span class="na">toLowerCase</span><span class="o">();</span>
<span class="n">processEventsBasedOnMessageType</span><span class="o">(</span><span class="n">session</span><span class="o">,</span> <span class="n">messageType</span><span class="o">,</span> <span class="n">json</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">processEventsBasedOnMessageType</span><span class="o">(</span><span class="nc">Session</span> <span class="n">session</span><span class="o">,</span> <span class="nc">String</span> <span class="n">messageType</span><span class="o">,</span> <span class="nc">JSONObject</span> <span class="n">json</span><span class="o">)</span> <span class="o">{</span>
<span class="k">switch</span> <span class="o">(</span><span class="n">messageType</span><span class="o">)</span> <span class="o">{</span>
<span class="k">case</span> <span class="s">"heartbeatreq"</span><span class="o">:</span>
<span class="nc">SystemFactory</span><span class="o">.</span><span class="na">getInstance</span><span class="o">().</span><span class="na">getWebSocketMessageHandler</span><span class="o">().</span><span class="na">handleHeartBeatMessages</span><span class="o">(</span><span class="n">session</span><span class="o">);</span>
<span class="k">break</span><span class="o">;</span>
<span class="k">default</span><span class="o">:</span>
<span class="n">logger</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Unknown message type received : {}"</span><span class="o">,</span> <span class="n">messageType</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>For the load testing part, we will use the popular <a href="https://locust.io/">Locust</a> load testing framework.</p>
<p>We use <a href="https://github.com/websocket-client/websocket-client/">websocket-client</a> to manage websockets inside python. It is a very elegant library which allows to handle both short lived and long lived websocket connections.</p>
<p>Whenever we send the heartbeat request to server, we send a timestamp from the client side in the field <code class="language-plaintext highlighter-rouge">publishTimestamp</code>. The server then responds with the <code class="language-plaintext highlighter-rouge">heartbeatresp</code> and also includes the original timestamp <code class="language-plaintext highlighter-rouge">publishTimestamp</code> which was sent in the request in the response. The server also includes the <code class="language-plaintext highlighter-rouge">serverDelay</code> which represents the delay of server in processing the heartbeat response.</p>
<p>Our contract looks like this:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"heartbeatreq"</span><span class="p">,</span><span class="w">
</span><span class="nl">"userId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
</span><span class="nl">"publishTimestamp"</span><span class="p">:</span><span class="w"> </span><span class="mi">1722758706760</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"heartbeatresp"</span><span class="p">,</span><span class="w">
</span><span class="nl">"userId"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
</span><span class="nl">"serverDelay"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
</span><span class="nl">"clientPublishTimestamp"</span><span class="p">:</span><span class="w"> </span><span class="mi">1722758706760</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>We can subtract the current time (the time we received the message in locust) and the <code class="language-plaintext highlighter-rouge">clientPublishTimestamp</code> in the <code class="language-plaintext highlighter-rouge">heartbeatresp</code> to calculate the end to end time of processing the heartbeat request message.</p>
<p>When running the load test with Locust, it’s important to ensure that Locust itself is not experiencing high CPU usage. Otherwise, the high latency could be attributed to the lag in receiving the messages in the locust itself, rather than the processing time of the server.</p>
<p>We run the load with 10K connected users with ramp up rate of 500. I choose this number because I am running this on my local desktop computer.</p>
<p>This means we will be connecting 500 websockets / s until we reach total 10K connected sockets on the server.</p>
<p><a href="/images/Blog/locust_start.png" data-fancybox="">
<img src="/images/Blog/locust_start.png" width="1000px" />
</a></p>
<p>Locust will take some time to spawn all the 10K users. At this point, we can see that rate of <code class="language-plaintext highlighter-rouge">ws_connect</code> is 500 per second.</p>
<p><a href="/images/Blog/locust_spawn.png" data-fancybox="">
<img src="/images/Blog/locust_spawn.png" width="1000px" />
</a></p>
<p>Once the spawn state is finished, all the websockets are connected. At this point, all the connected websockets will be sending heartbeat requests at every 2 seconds, which comes out to ~ 5000 heartbeat requests per second as shown below.</p>
<p><a href="/images/Blog/locust_running.png" data-fancybox="">
<img src="/images/Blog/locust_running.png" width="1000px" />
</a></p>
<h3 id="conclusion">Conclusion</h3>
<p>In this article, we saw how to load test WebSocket servers using locust. The benefit of using locust is to have fine grained control over number of sockets to connect, when to trigger messages as well as calculate end to end latency.</p>
<p>The entire code of this article is present on this <a href="https://github.com/madhur/load-test-websocket-locust">github repository</a></p>
<p>Feel free to use this code or if you have any comments, add comment using the comment box below.</p>
Monitoring Redis Streams lag in Prometheus and Grafana2024-07-14T00:00:00+05:30id:/blog/2024/07/14/monitoring-redis-streams-lag-prometheus-grafana<p>Monitoring Redis Streams lag in Prometheus and Grafana</p>
<p>In this post, we will look at how we can measure redis streams lag from the application perspective.</p>
<p>We would want to measure the lag and plot this in visual format using Prometheus in Grafana</p>
<h3 id="assumptions">Assumptions</h3>
<ul>
<li>
<p>We are going to assume that we have set of streams and set of consumer groups which are consuming from the stream. We are using consumer groups because we want high availability to consume via multiple nodes. We assume that each consumer within consumer groups are dynamic, for example if a consumer is running on a kubernetes node, a node can be restarted, respawned.</p>
</li>
<li>
<p>We are also going to assume that we are trimming stream length to some constant number of messages at regular intervals to save memory. This is important in terms of monitoring since redis reports some metrics assuming that consumer group has been consuming from beginning of stream, which many a times is not the case.</p>
</li>
<li>
<p>We are also going to assume that there is some constant minimum rate of message publish on redis stream. Why do we need this assumption? Not enforcing this restriction makes the monitoring even more difficult. We will see why below.</p>
</li>
<li>
<p>In the examples below, we have streams named <code class="language-plaintext highlighter-rouge">ack0, ack1, ack2, ack3, ack4</code> consumed by consumer group <code class="language-plaintext highlighter-rouge">poker-coordinator-ack</code></p>
</li>
</ul>
<h3 id="how-do-we-define-lag">How do we define lag?</h3>
<p>In terms of Redis streams, we are primarily interested in two metrics:</p>
<ul>
<li>
<p>Undelivered messages: The messages which have been published on redis stream, but not yet have been consumed by the consumer group. This can happen if consumer group is taking a lot of time to process messages. This indicates that either we need to scale horizontally to increase the consumption rate or reduce the publish rate.</p>
</li>
<li>
<p>Pending messages: The message which have been consumed by the consumer group but yet not acknowledged. This is only applicable for redis streams consumer groups which have acknowledgement enabled. The messages which are not acknowledged, remain in <a href="https://redis.io/docs/latest/commands/xpending/">Pending Entries List</a> data structure. It is important to know the length of this data structure to determine if there were some messages in stream which were not acknowledged by the consumer.</p>
</li>
</ul>
<p>We will now examine each of the redis stream related command output below to determine if it can help in monitoring redis streams.</p>
<h3 id="xinfo-groups-ack0-will-give-information-about-ack0-stream-consumer-groups">xinfo groups ack0, will give information about ack0 stream consumer groups.</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>127.0.0.1:6381> xinfo groups ack3
1) 1) "name"
2) "consumerGroup"
3) "consumers"
4) (integer) 2
5) "pending"
6) (integer) 0
7) "last-delivered-id"
8) "0-0"
9) "entries-read"
10) (nil)
11) "lag"
12) (integer) 8766
2) 1) "name"
2) "poker-coordinator-ack"
3) "consumers"
4) (integer) 20
5) "pending"
6) (integer) 100
7) "last-delivered-id"
8) "1720934812760-0"
9) "entries-read"
10) (integer) 791553
11) "lag"
12) (integer) 11445
</code></pre></div></div>
<p>This output gives the number of consumer groups for stream <code class="language-plaintext highlighter-rouge">ack3</code>. Here, stream <code class="language-plaintext highlighter-rouge">ack3</code> has two consumer groups namely <code class="language-plaintext highlighter-rouge">consumerGroup</code> and <code class="language-plaintext highlighter-rouge">poker-coordinator-ack</code>.</p>
<ul>
<li>
<p>The number of consumers in both groups are 2 and 20 respectively.</p>
</li>
<li>
<p>The pending messages in both groups are 0 and 100 respectively.</p>
</li>
<li>
<p>The last delivered id for both groups is <code class="language-plaintext highlighter-rouge">0-0</code> and <code class="language-plaintext highlighter-rouge">1720934812760-0</code> respectively. The first one indicates that its dead and is not running. This metric is useful in monitoring since it can tell us which was the last id which was delivered to this consumer group by redis stream.</p>
</li>
<li>
<p>The entries read for both groups are <code class="language-plaintext highlighter-rouge">nil</code> and <code class="language-plaintext highlighter-rouge">791553</code> respectively. This number is lifetime aggregate and is usually useless for monitoring purposes. This is because stream length can generally be trimmed at regular intervals for saving memory as I mentioned in the assumptions. Once the stream length is trimmed, this number in comparison to stream length is very large.</p>
</li>
<li>
<p>The lag for both groups is 8766 and 11445 respectively. The lag is important metric, however its important to understand it.</p>
</li>
</ul>
<blockquote>
<p>The lag of a given consumer group is the number of entries in the range between the group’s entries_read and the stream’s entries_added. Put differently, it is the number of entries that are yet to be delivered to the group’s consumers.</p>
</blockquote>
<p>It seems that this number is a good metric to monitor for monitoring. However, there is a caveat to it. The way redis calculates this number is by subtracting the total number of messages published on stream minus the number of messages read by consumer group since beginning. In real world, the consumer group might have been created later after the stream is created OR there might have been some messages which were trimmed or deleted from the stream which this consumer group might never receieve. In that case, this number will always report very large value which is not what we want.</p>
<h3 id="xpending-ack3-poker-coordinator-ack-will-give-the-information-about-pending-messages-to-the-consumer-group">xpending ack3 poker-coordinator-ack will give the information about pending messages to the consumer group</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>127.0.0.1:6379> xpending ack3 poker-coordinator-ack
-> Redirected to slot [15549] located at 127.0.0.1:6381
1) (integer) 100
2) "1720937728800-4"
3) "1720937738803-1"
4) 1) 1) "F8DD1276E0DF496C965EC775CA07397F"
2) "100"
</code></pre></div></div>
<p>This outputs that there are <code class="language-plaintext highlighter-rouge">100</code> pending messages. The smallest message id is <code class="language-plaintext highlighter-rouge">1720937728800-4</code> and largest message id is <code class="language-plaintext highlighter-rouge">1720937738803-1</code>. The consumer (within the group <code class="language-plaintext highlighter-rouge">poker-coordinator-ack</code>) which has pending messages 100 with id <code class="language-plaintext highlighter-rouge">F8DD1276E0DF496C965EC775CA07397F</code></p>
<h3 id="xinfo-consumers-ack3-poker-coordinator-ack">xinfo consumers ack3 poker-coordinator-ack</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 1) 1) "name"
2) "07A1A45C9BCD40238A54F773ECB47B82"
3) "pending"
4) (integer) 0
5) "idle"
6) (integer) 1463079
7) "inactive"
8) (integer) 1463079
2) 1) "name"
2) "225EF592894744ADB4F0768797F608CE"
3) "pending"
4) (integer) 0
5) "idle"
6) (integer) 10170676
7) "inactive"
8) (integer) 10170676
</code></pre></div></div>
<p>The output above gives information about each unique consumer within a consumer group. Since our consumers are dynamic in a containerized infrastructure, the consumer group name is just a UUID. Once the container goes away, the unique consumer name is no longer relevant and we are not interested in the information. It is apparent that we are not interested in each consumer within a group. We want to make sure that a consumer group as a whole is healthy and consuming messages at healthy rate.</p>
<h3 id="calculating-undelivered-messages">Calculating undelivered messages</h3>
<p>The best way to calculate undelivered message is get the last delivered ID of the consumer group and the last delivered ID of the stream. Count the number of messages between them and report it as lag. Here is the pseudo code using <a href="https://github.com/redisson/redisson">Redisson</a> client in java.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">org.redisson.api.StreamMessageId</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.redisson.api.stream.StreamReadGroupArgs</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">updateStreamLagMetrics</span><span class="o">(</span><span class="nc">RedissonClient</span> <span class="n">redisson</span><span class="o">,</span> <span class="nc">String</span> <span class="n">streamName</span><span class="o">,</span> <span class="nc">String</span> <span class="n">groupName</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">RStream</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">String</span><span class="o">></span> <span class="n">stream</span> <span class="o">=</span> <span class="n">redisson</span><span class="o">.</span><span class="na">getStream</span><span class="o">(</span><span class="n">streamName</span><span class="o">);</span>
<span class="c1">// Get stream info</span>
<span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">></span> <span class="n">streamInfo</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="na">getInfo</span><span class="o">();</span>
<span class="nc">String</span> <span class="n">lastGeneratedId</span> <span class="o">=</span> <span class="o">(</span><span class="nc">String</span><span class="o">)</span> <span class="n">streamInfo</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"lastGeneratedId"</span><span class="o">);</span>
<span class="c1">// Get group info</span>
<span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">></span> <span class="n">groupInfo</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="na">listGroups</span><span class="o">().</span><span class="na">stream</span><span class="o">()</span>
<span class="o">.</span><span class="na">filter</span><span class="o">(</span><span class="n">group</span> <span class="o">-></span> <span class="n">group</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"name"</span><span class="o">).</span><span class="na">equals</span><span class="o">(</span><span class="n">groupName</span><span class="o">))</span>
<span class="o">.</span><span class="na">findFirst</span><span class="o">()</span>
<span class="o">.</span><span class="na">orElseThrow</span><span class="o">(()</span> <span class="o">-></span> <span class="k">new</span> <span class="nc">RuntimeException</span><span class="o">(</span><span class="s">"Group not found"</span><span class="o">));</span>
<span class="nc">String</span> <span class="n">lastDeliveredId</span> <span class="o">=</span> <span class="o">(</span><span class="nc">String</span><span class="o">)</span> <span class="n">groupInfo</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"lastDeliveredId"</span><span class="o">);</span>
<span class="c1">// Get pending messages count</span>
<span class="nc">PendingResult</span> <span class="n">pendingInfo</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="na">getPendingInfo</span><span class="o">(</span><span class="n">groupName</span><span class="o">);</span>
<span class="kt">long</span> <span class="n">pendingCount</span> <span class="o">=</span> <span class="n">pendingInfo</span><span class="o">.</span><span class="na">getTotal</span><span class="o">();</span>
<span class="c1">// Calculate undelivered messages</span>
<span class="kt">long</span> <span class="n">undeliveredMessages</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">lastGeneratedId</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">lastDeliveredId</span><span class="o">))</span> <span class="o">{</span>
<span class="nc">StreamMessageId</span> <span class="n">lastDeliveredStreamId</span> <span class="o">=</span> <span class="n">parseStreamMessageId</span><span class="o">(</span><span class="n">lastDeliveredId</span><span class="o">);</span>
<span class="c1">// Read all messages after the last delivered ID</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">StreamEntry</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">String</span><span class="o">>></span> <span class="n">undeliveredEntries</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="na">readGroup</span><span class="o">(</span><span class="n">groupName</span><span class="o">,</span> <span class="s">"temp-consumer"</span><span class="o">,</span>
<span class="nc">StreamReadGroupArgs</span><span class="o">.</span><span class="na">neverDelivered</span><span class="o">().</span><span class="na">count</span><span class="o">(</span><span class="nc">Integer</span><span class="o">.</span><span class="na">MAX_VALUE</span><span class="o">));</span>
<span class="n">undeliveredMessages</span> <span class="o">=</span> <span class="n">undeliveredEntries</span><span class="o">.</span><span class="na">size</span><span class="o">();</span>
<span class="c1">// Remove the temporary consumer</span>
<span class="n">stream</span><span class="o">.</span><span class="na">removeConsumer</span><span class="o">(</span><span class="n">groupName</span><span class="o">,</span> <span class="s">"temp-consumer"</span><span class="o">);</span>
<span class="o">}</span>
<span class="kt">long</span> <span class="n">totalLag</span> <span class="o">=</span> <span class="n">undeliveredMessages</span> <span class="o">+</span> <span class="n">pendingCount</span><span class="o">;</span>
<span class="c1">// Update Prometheus metrics</span>
<span class="no">UNDELIVERED_MESSAGES</span><span class="o">.</span><span class="na">labels</span><span class="o">(</span><span class="n">streamName</span><span class="o">,</span> <span class="n">groupName</span><span class="o">).</span><span class="na">set</span><span class="o">(</span><span class="n">undeliveredMessages</span><span class="o">);</span>
<span class="no">PENDING_MESSAGES</span><span class="o">.</span><span class="na">labels</span><span class="o">(</span><span class="n">streamName</span><span class="o">,</span> <span class="n">groupName</span><span class="o">).</span><span class="na">set</span><span class="o">(</span><span class="n">pendingCount</span><span class="o">);</span>
<span class="no">TOTAL_LAG</span><span class="o">.</span><span class="na">labels</span><span class="o">(</span><span class="n">streamName</span><span class="o">,</span> <span class="n">groupName</span><span class="o">).</span><span class="na">set</span><span class="o">(</span><span class="n">totalLag</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="nc">StreamMessageId</span> <span class="nf">parseStreamMessageId</span><span class="o">(</span><span class="nc">String</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">String</span><span class="o">[]</span> <span class="n">parts</span> <span class="o">=</span> <span class="n">id</span><span class="o">.</span><span class="na">split</span><span class="o">(</span><span class="s">"-"</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">parts</span><span class="o">.</span><span class="na">length</span> <span class="o">!=</span> <span class="mi">2</span><span class="o">)</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalArgumentException</span><span class="o">(</span><span class="s">"Invalid stream ID format: "</span> <span class="o">+</span> <span class="n">id</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">StreamMessageId</span><span class="o">(</span><span class="nc">Long</span><span class="o">.</span><span class="na">parseLong</span><span class="o">(</span><span class="n">parts</span><span class="o">[</span><span class="mi">0</span><span class="o">]),</span> <span class="nc">Long</span><span class="o">.</span><span class="na">parseLong</span><span class="o">(</span><span class="n">parts</span><span class="o">[</span><span class="mi">1</span><span class="o">]));</span>
<span class="o">}</span>
</code></pre></div></div>
<p>However, note that counting the number of messages between two IDs is a very expensive operation which I do not recommend doing on a production cluster. Instead, what we can do is get the approximation of time difference between the two and report it as a metric. This is where the assumption that <code class="language-plaintext highlighter-rouge">there is some constant minimum rate of message publish on redis stream.</code> comes into picture. If the messages are sparingly published, this approach does not work.</p>
<p>Here is the updated code based on the assumption</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">computeStreamLag</span><span class="o">(</span><span class="nc">RedissonClient</span> <span class="n">redisson</span><span class="o">,</span> <span class="nc">String</span> <span class="n">streamName</span><span class="o">,</span> <span class="nc">String</span> <span class="n">groupName</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">RStream</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">String</span><span class="o">></span> <span class="n">stream</span> <span class="o">=</span> <span class="n">redisson</span><span class="o">.</span><span class="na">getStream</span><span class="o">(</span><span class="n">streamName</span><span class="o">);</span>
<span class="c1">// Get stream info</span>
<span class="nc">StreamInfo</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">String</span><span class="o">></span> <span class="n">streamInfo</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="na">getInfo</span><span class="o">();</span>
<span class="nc">StreamMessageId</span> <span class="n">lastGeneratedId</span> <span class="o">=</span> <span class="n">streamInfo</span><span class="o">.</span><span class="na">getLastGeneratedId</span><span class="o">();</span>
<span class="c1">// Get group info</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">StreamGroup</span><span class="o">></span> <span class="n">groupsInfo</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="na">listGroups</span><span class="o">();</span>
<span class="nc">StreamGroup</span> <span class="n">groupInfo</span> <span class="o">=</span> <span class="n">groupsInfo</span><span class="o">.</span><span class="na">stream</span><span class="o">()</span>
<span class="o">.</span><span class="na">filter</span><span class="o">(</span><span class="n">group</span> <span class="o">-></span> <span class="n">group</span><span class="o">.</span><span class="na">getName</span><span class="o">().</span><span class="na">equals</span><span class="o">(</span><span class="n">groupName</span><span class="o">))</span>
<span class="o">.</span><span class="na">findFirst</span><span class="o">()</span>
<span class="o">.</span><span class="na">orElseThrow</span><span class="o">(()</span> <span class="o">-></span> <span class="k">new</span> <span class="nc">RuntimeException</span><span class="o">(</span><span class="s">"Group not found"</span><span class="o">));</span>
<span class="nc">StreamMessageId</span> <span class="n">lastDeliveredId</span> <span class="o">=</span> <span class="n">groupInfo</span><span class="o">.</span><span class="na">getLastDeliveredId</span><span class="o">();</span>
<span class="c1">// Get pending messages count</span>
<span class="nc">PendingResult</span> <span class="n">pendingInfo</span> <span class="o">=</span> <span class="n">stream</span><span class="o">.</span><span class="na">getPendingInfo</span><span class="o">(</span><span class="n">groupName</span><span class="o">);</span>
<span class="kt">long</span> <span class="n">pendingCount</span> <span class="o">=</span> <span class="n">pendingInfo</span><span class="o">.</span><span class="na">getTotal</span><span class="o">();</span>
<span class="c1">// Calculate lag</span>
<span class="c1">// Calculate undelivered messages</span>
<span class="kt">long</span> <span class="n">undeliveredMessages</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">lastGeneratedId</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="n">lastDeliveredId</span><span class="o">))</span> <span class="o">{</span>
<span class="c1">// If last generated ID is different from last delivered ID, there are undelivered messages</span>
<span class="n">undeliveredMessages</span> <span class="o">=</span> <span class="n">calculateIdDifference</span><span class="o">(</span><span class="n">lastGeneratedId</span><span class="o">,</span> <span class="n">lastDeliveredId</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">if</span> <span class="o">(</span><span class="n">undeliveredMessages</span> <span class="o"><</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="n">undeliveredMessages</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
<span class="o">}</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Tag</span><span class="o">></span> <span class="n">undeliveredMessagesTag</span> <span class="o">=</span>
<span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><>(</span><span class="nc">Collections</span><span class="o">.</span><span class="na">singleton</span><span class="o">(</span><span class="nc">Tag</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"type"</span><span class="o">,</span> <span class="s">"undeliveredMessages"</span><span class="o">)));</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Tag</span><span class="o">></span> <span class="n">pendingMessages</span> <span class="o">=</span>
<span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><>(</span><span class="nc">Collections</span><span class="o">.</span><span class="na">singleton</span><span class="o">(</span><span class="nc">Tag</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="s">"type"</span><span class="o">,</span> <span class="s">"pendingMessages"</span><span class="o">)));</span>
<span class="k">this</span><span class="o">.</span><span class="na">prometheusMetricWrapper</span><span class="o">.</span><span class="na">recordGaugeValue</span><span class="o">(</span><span class="n">streamName</span><span class="o">,</span> <span class="n">undeliveredMessagesTag</span><span class="o">,</span> <span class="n">undeliveredMessages</span><span class="o">);</span>
<span class="k">this</span><span class="o">.</span><span class="na">prometheusMetricWrapper</span><span class="o">.</span><span class="na">recordGaugeValue</span><span class="o">(</span><span class="n">streamName</span><span class="o">,</span> <span class="n">pendingMessages</span><span class="o">,</span> <span class="n">pendingCount</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kt">long</span> <span class="nf">calculateIdDifference</span><span class="o">(</span><span class="nc">StreamMessageId</span> <span class="n">id1</span><span class="o">,</span> <span class="nc">StreamMessageId</span> <span class="n">id2</span><span class="o">)</span> <span class="o">{</span>
<span class="kt">long</span> <span class="n">timeDiff</span> <span class="o">=</span> <span class="n">id1</span><span class="o">.</span><span class="na">getId0</span><span class="o">()</span> <span class="o">-</span> <span class="n">id2</span><span class="o">.</span><span class="na">getId0</span><span class="o">();</span>
<span class="kt">long</span> <span class="n">seqDiff</span> <span class="o">=</span> <span class="n">id1</span><span class="o">.</span><span class="na">getId1</span><span class="o">()</span> <span class="o">-</span> <span class="n">id2</span><span class="o">.</span><span class="na">getId1</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">timeDiff</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">seqDiff</span><span class="o">;</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="c1">// This is an approximation. Actual difference might be slightly different</span>
<span class="k">return</span> <span class="o">(</span><span class="n">timeDiff</span><span class="o">)</span> <span class="o">+</span> <span class="n">seqDiff</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="nc">StreamMessageId</span> <span class="nf">parseStreamMessageId</span><span class="o">(</span><span class="nc">String</span> <span class="n">id</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">String</span><span class="o">[]</span> <span class="n">parts</span> <span class="o">=</span> <span class="n">id</span><span class="o">.</span><span class="na">split</span><span class="o">(</span><span class="s">"-"</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">parts</span><span class="o">.</span><span class="na">length</span> <span class="o">!=</span> <span class="mi">2</span><span class="o">)</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalArgumentException</span><span class="o">(</span><span class="s">"Invalid stream ID format: "</span> <span class="o">+</span> <span class="n">id</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">StreamMessageId</span><span class="o">(</span><span class="nc">Long</span><span class="o">.</span><span class="na">parseLong</span><span class="o">(</span><span class="n">parts</span><span class="o">[</span><span class="mi">0</span><span class="o">]),</span> <span class="nc">Long</span><span class="o">.</span><span class="na">parseLong</span><span class="o">(</span><span class="n">parts</span><span class="o">[</span><span class="mi">1</span><span class="o">]));</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="what-about-measuring-reliable-processing-lag">What about measuring reliable processing Lag</h3>
<p>We saw above that it is not possible to measure the redis streams lag (measured as count of mesasges) accurately if the rate of publish on the stream varies a lot.</p>
<p>How did we end up solving this?</p>
<p>One of the other ways of solving is to let publisher add the timestamp to the each of stream message. This way, when the message arrives at the consumer, you can calculate the difference and plot it as processing lag.</p>
<h3 id="conclusion">Conclusion</h3>
<p>In this article we saw how to monitor redis streams lag. Here is the final snapshot of the dashboard which came out as a result of this excercise.</p>
<p><a href="/images/grafana_stream.png" data-fancybox="">
<img src="/images/grafana_stream.png" width="1000px" />
</a></p>
Faking time in JVM Process2024-07-01T00:00:00+05:30id:/blog/2024/07/01/mocking-jvm-time<p>Recently, we were doing automation testing for one of our backend services and encountered an unique scenario wherein we wanted to forward the time by 24 hrs because we had entities which were created on daily basis at certain time.</p>
<p>To automate these scenarios, we had to wait for 24 unless we did either of these things:</p>
<ul>
<li>
<p>Somehow create those entities every few minutes instead of every 24 hours - This would require making the implementation generic for both scenarios.</p>
</li>
<li>
<p>Fast forward the system time - Since we were running in containers and containers inherit the date time settings of host machine, we had to change the time of host machine. However, that means lot of other containers running in the same machine getting affected by these side effect.</p>
</li>
</ul>
<p>We did not go with first option either, since there were lot of backend validations implemented and removing those validations means making the implementation susceptible to errors.</p>
<p>We looked through many solutions and finally arrived by using a library called <a href="https://github.com/faketime-java/faketime">faketime</a></p>
<p>In short,</p>
<blockquote>
<p>FakeTime uses a native Java agent to replace <code class="language-plaintext highlighter-rouge">System.currentTimeMillis()</code> implementation with the one you can control using system properties.</p>
</blockquote>
<h3 id="getting-started-with-faketime">Getting started with Faketime</h3>
<p>Faketime is implemented as a native agent and hence it needs to be built for every platform such as Linux, Mac OS x86, Mac M1 etc.</p>
<p>Fortunately, there exists maven modules for each of the architectures</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!-- Windows 32bit --></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>io.github.faketime-java<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>faketime-agent<span class="nt"></artifactId></span>
<span class="nt"><version></span>0.8.0<span class="nt"></version></span>
<span class="nt"><classifier></span>windows32<span class="nt"></classifier></span>
<span class="nt"></dependency></span>
<span class="c"><!-- Windows 64bit --></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>io.github.faketime-java<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>faketime-agent<span class="nt"></artifactId></span>
<span class="nt"><version></span>0.8.0<span class="nt"></version></span>
<span class="nt"><classifier></span>windows64<span class="nt"></classifier></span>
<span class="nt"></dependency></span>
<span class="c"><!-- macOS 32bit --></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>io.github.faketime-java<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>faketime-agent<span class="nt"></artifactId></span>
<span class="nt"><version></span>0.8.0<span class="nt"></version></span>
<span class="nt"><classifier></span>mac32<span class="nt"></classifier></span>
<span class="nt"></dependency></span>
<span class="c"><!-- macOS 64bit --></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>io.github.faketime-java<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>faketime-agent<span class="nt"></artifactId></span>
<span class="nt"><version></span>0.8.0<span class="nt"></version></span>
<span class="nt"><classifier></span>mac64<span class="nt"></classifier></span>
<span class="nt"></dependency></span>
<span class="c"><!-- Linux 32bit --></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>io.github.faketime-java<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>faketime-agent<span class="nt"></artifactId></span>
<span class="nt"><version></span>0.8.0<span class="nt"></version></span>
<span class="nt"><classifier></span>linux32<span class="nt"></classifier></span>
<span class="nt"></dependency></span>
<span class="c"><!-- Linux 64bit --></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>io.github.faketime-java<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>faketime-agent<span class="nt"></artifactId></span>
<span class="nt"><version></span>0.8.0<span class="nt"></version></span>
<span class="nt"><classifier></span>linux64<span class="nt"></classifier></span>
<span class="nt"></dependency></span>
</code></pre></div></div>
<p>I have implemented a <a href="https://github.com/madhur/faketime-example">small github project</a> to demonstrate faking the time using this library.</p>
<p>Here is how you would run it…</p>
<h3 id="run-using-maven">Run using Maven</h3>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mvn spring-boot:run <span class="nt">-Dspring-boot</span>.run.jvmArguments<span class="o">=</span><span class="s2">"-agentpath:./src/main/resources/libfaketime -XX:+UnlockDiagnosticVMOptions -XX:DisableIntrinsic=_currentTimeMillis -XX:CompileCommand=quiet -XX:CompileCommand=exclude,java/lang/System.currentTimeMillis -XX:CompileCommand=exclude,jdk/internal/misc/VM.getNanoTimeAdjustment"</span>
</code></pre></div></div>
<h3 id="get-current-time">Get current time</h3>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">--location</span> <span class="s1">'localhost:8080/time/getTime'</span>
<span class="o">{</span>
<span class="s2">"localDateTime"</span>: <span class="s2">"2024-06-29T15:42:41.973356"</span>,
<span class="s2">"timestamp"</span>: 1719655961973,
<span class="s2">"instant"</span>: <span class="s2">"2024-06-29T10:12:41.973377Z"</span>,
<span class="s2">"date"</span>: <span class="s2">"2024-06-29T10:12:41.973+00:00"</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="set-fake-time">Set fake time</h3>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">--location</span> <span class="nt">--request</span> POST <span class="s1">'localhost:8080/time/setTime?dateTime=2024-08-01T00%3A00%3A00'</span>
Time <span class="nb">set</span>
</code></pre></div></div>
<h3 id="get-current-time-1">Get current time</h3>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">--location</span> <span class="s1">'localhost:8080/time/getTime'</span>
<span class="o">{</span>
<span class="s2">"localDateTime"</span>: <span class="s2">"2024-08-01T00:00:35.085"</span>,
<span class="s2">"timestamp"</span>: 1722450635085,
<span class="s2">"instant"</span>: <span class="s2">"2024-07-31T18:30:35.085Z"</span>,
<span class="s2">"date"</span>: <span class="s2">"2024-07-31T18:30:35.085+00:00"</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="reset-back-to-real-time">Reset back to real time</h3>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">--location</span> <span class="nt">--request</span> POST <span class="s1">'localhost:8080/time/resetTime'</span>
</code></pre></div></div>
Rtkit Daemon flooding journalctl logs2024-06-22T00:00:00+05:30id:/blog/2024/06/22/journalctl-rtkit-daemon<p>Recently I observed, <a href="https://github.com/heftig/rtkit">Rtkit daemon</a> has been flooding the <a href="https://www.freedesktop.org/software/systemd/man/latest/journalctl.html">Journalctl</a> logs</p>
<p>I frequently found logs such as below in the journalctl logs</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Jun 13 22:51:50 madhur-b550mds3h rtkit-daemon[1922]: Supervising 2 threads of 1 processes of 1 users.
Jun 13 22:51:50 madhur-b550mds3h rtkit-daemon[1922]: Supervising 2 threads of 1 processes of 1 users.
Jun 13 22:51:50 madhur-b550mds3h rtkit-daemon[1922]: Supervising 2 threads of 1 processes of 1 users.
Jun 13 22:51:50 madhur-b550mds3h rtkit-daemon[1922]: Supervising 2 threads of 1 processes of 1 users.
...
Jun 12 23:48:39 madhur-b550mds3h rtkit-daemon[1942]: Failed to look up client: No such file or directory
Jun 12 23:48:42 madhur-b550mds3h rtkit-daemon[1942]: Failed to look up client: No such file or directory
Jun 12 23:48:44 madhur-b550mds3h rtkit-daemon[1942]: Failed to look up client: No such file or directory
</code></pre></div></div>
<p>Not much aware about this daemon, however, it seems to be used by audio systems such as <a href="https://wiki.archlinux.org/title/PulseAudio">Pulseaudio</a> and <a href="https://wiki.archlinux.org/title/PipeWire">Pipewire</a></p>
<p>Fixing these logs is simple</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl edit rtkit-daemon
</code></pre></div></div>
<p>Add the following lines</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Service]
LogLevelMax=warning
</code></pre></div></div>
<p>And restart rtkit daemon using <code class="language-plaintext highlighter-rouge">systemctl restart rtkit-daemon</code></p>
My Homelab Update - Part I2024-05-01T00:00:00+05:30id:/blog/2024/05/01/my-homelab-update-part1<p><a href="/blog/2024/01/07/proxmox-user-friendly-urls.html">Continuing my Homelab journey using Proxmox</a>, I have setup a full fledged home network with multiple services running.</p>
<p>Here is the quick diagram below for reference:</p>
<p><img src="/images/Blog/homenetwork.png" alt="" /></p>
<p>Some of the services I am running on my homenetwork as of now are</p>
<ul>
<li><a href="https://github.com/jellyfin/jellyfin">Jellyfin</a></li>
<li><a href="https://github.com/Athou/commafeed">Commmafeed</a></li>
<li><a href="https://github.com/prometheus/prometheus">Prometheus</a></li>
<li><a href="https://github.com/grafana/grafana">Grafana</a></li>
<li><a href="https://github.com/NginxProxyManager/nginx-proxy-manager">Nginx Proxy Manager</a></li>
<li><a href="https://snapdrop.net/">Snapdrop</a></li>
<li><a href="https://github.com/louislam/uptime-kuma">Uptime Kuma</a></li>
<li><a href="https://github.com/dgtlmoon/changedetection.io">Change Detection</a></li>
<li><a href="https://github.com/qbittorrent/qBittorrent">qBittorrent</a></li>
<li><a href="https://github.com/OliveTin/OliveTin">Olivetin</a></li>
<li><a href="https://github.com/dani-garcia/vaultwarden">Vaultwarden</a></li>
<li><a href="https://github.com/binwiederhier/ntfy">ntfy</a></li>
</ul>
<p>Some of the things I am trying to solve:</p>
<ul>
<li>Easily able to VPN from outside to access my password manager, RSS feeds, media etc</li>
<li>Get rid of <a href="https://www.pfsense.org/">PfSense</a>, PfSense is good but I feel its overkill for my needs.</li>
<li>Evaluate <a href="https://tailscale.com/">Tailscale</a>, <a href="https://www.wireguard.com/">Wireguard</a> and <a href="https://openvpn.net/">OpenVPN</a></li>
</ul>
<p>And finally,</p>
<ul>
<li>Having less power bill while powering my Proxmox server 24x7 :)</li>
</ul>
<p>Stay tuned for further updates if you are interested.</p>
Quick Websockets client in Python2024-04-13T00:00:00+05:30id:/blog/2024/04/13/quick-websockets-client-python<p>When interacting with lot of websockets server, you need a nifty client to test out the behaviour of websocket server.</p>
<p>Till now, I have been using <a href="https://github.com/websockets/wscat">wscat</a> as the command line utility to interact with websockets server.</p>
<p>However, this command line utility has several disadvantages:</p>
<ul>
<li>It doesn’t allow you to send custom ping messages</li>
<li>It doesn’t support authentication</li>
</ul>
<p>Due to these limitations, I wrote a simple python script below which can send a custom ping payload.</p>
<p>This little script helps to quickly test out websocket servers.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">websocket</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="n">session_id</span><span class="o">=</span><span class="s">"12345"</span>
<span class="n">ready_message</span> <span class="o">=</span> <span class="p">{</span><span class="s">"type"</span><span class="p">:</span><span class="s">"setup"</span><span class="p">,</span><span class="s">"sid"</span><span class="p">:</span><span class="n">session_id</span><span class="p">,</span><span class="s">"time"</span><span class="p">:</span><span class="mi">1712393684026</span><span class="p">,</span> <span class="s">"src"</span><span class="p">:</span> <span class="s">"79677227"</span><span class="p">}</span>
<span class="n">ping_payload</span><span class="o">=</span><span class="p">{</span><span class="s">"type"</span><span class="p">:</span><span class="s">"heartbeatreq"</span><span class="p">,</span><span class="s">"time"</span><span class="p">:</span><span class="mi">1712393714042</span><span class="p">,</span><span class="s">"src"</span><span class="p">:</span><span class="s">"79677227"</span><span class="p">}</span>
<span class="k">def</span> <span class="nf">on_message</span><span class="p">(</span><span class="n">wsapp</span><span class="p">,</span> <span class="n">message</span><span class="p">):</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Received: "</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">on_ping</span><span class="p">(</span><span class="n">wsapp</span><span class="p">,</span> <span class="n">message</span><span class="p">):</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Got a ping! A pong reply has already been automatically sent."</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">on_pong</span><span class="p">(</span><span class="n">wsapp</span><span class="p">,</span> <span class="n">message</span><span class="p">):</span>
<span class="n">wsapp</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">on_open</span><span class="p">(</span><span class="n">wsapp</span><span class="p">):</span>
<span class="k">print</span><span class="p">(</span><span class="s">"sending ready"</span><span class="p">)</span>
<span class="n">ready_msg</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">ready_message</span><span class="p">)</span> <span class="o">+</span> <span class="s">"</span><span class="se">\n</span><span class="s">"</span>
<span class="n">wsapp</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="n">ready_msg</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">on_error</span><span class="p">(</span><span class="n">wsapp</span><span class="p">,</span> <span class="n">message</span><span class="p">):</span>
<span class="k">print</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">on_close</span><span class="p">(</span><span class="n">wsapp</span><span class="p">,</span> <span class="n">close_status_code</span><span class="p">,</span> <span class="n">close_reason</span><span class="p">):</span>
<span class="k">print</span><span class="p">(</span><span class="s">"closed"</span><span class="p">,</span> <span class="n">close_reason</span><span class="p">,</span> <span class="n">close_status_code</span><span class="p">)</span>
<span class="n">wsapp</span> <span class="o">=</span> <span class="n">websocket</span><span class="p">.</span><span class="n">WebSocketApp</span><span class="p">(</span><span class="s">"ws://localhost:8080"</span><span class="p">,</span>
<span class="n">on_message</span><span class="o">=</span><span class="n">on_message</span><span class="p">,</span> <span class="n">on_ping</span><span class="o">=</span><span class="n">on_ping</span><span class="p">,</span> <span class="n">on_pong</span><span class="o">=</span><span class="n">on_pong</span><span class="p">,</span> <span class="n">on_open</span><span class="o">=</span><span class="n">on_open</span><span class="p">,</span> <span class="n">on_error</span><span class="o">=</span><span class="n">on_error</span><span class="p">,</span> <span class="n">on_close</span><span class="o">=</span><span class="n">on_close</span><span class="p">)</span>
<span class="n">wsapp</span><span class="p">.</span><span class="n">run_forever</span><span class="p">(</span><span class="n">ping_interval</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">ping_timeout</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">ping_payload</span><span class="o">=</span><span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">ping_payload</span><span class="p">)</span><span class="o">+</span> <span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>
</code></pre></div></div>
Building RediSearch module in Amazon Linux 22024-03-08T00:00:00+05:30id:/blog/2024/03/08/building-redisearch-module<p>RediSearch is a <a href="https://redis.io/">Redis</a> module that provides querying, secondary indexing, and full-text search for Redis. To use RediSearch, you first declare indexes on your Redis data. You can then use the RediSearch query language to query that data.</p>
<p>There is a very good <a href="https://github.com/RediSearch/redisearch-getting-started">RediSearch getting started</a> tutorial on github.</p>
<p>There are several steps in bulding RediSearch module. The following steps worked on Amazon Linux 2 machine for us:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yum <span class="nb">install </span>git perl perl-DateTime perl-JSON perl-Capture-Tiny
git clone https://github.com/RediSearch/RediSearch.git
wget https://github.com/linux-test-project/lcov/releases/download/v2.0/lcov-2.0-1.noarch.rpm
rpm <span class="nt">-ivh</span> /root/lcov-2.0-1.noarch.rpm
<span class="nb">cd </span>RediSearch
make setup
make build
</code></pre></div></div>
<p>At this point, the module should be built at the following location</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./RediSearch/bin/linux-x64-release/search/redisearch.so
</code></pre></div></div>
<p>Copy it to <code class="language-plaintext highlighter-rouge">/etc/redis</code></p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cp</span> /root/RediSearch/bin/linux-x64-release/search/redisearch.so /etc/redis/
</code></pre></div></div>
<p>The module can be activated in redis conf as follows:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>loadmodule /etc/redis/redisearch.so
</code></pre></div></div>
Websocket - Close Status codes2024-02-11T00:00:00+05:30id:/blog/2024/02/11/websocket-close-status-codes<h3 id="a-table-for-reference">A table for reference</h3>
<table>
<thead>
<tr>
<th>Close code (uint16)</th>
<th>Codename</th>
<th>Internal</th>
<th>Customizable</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>0 - 999</td>
<td> </td>
<td>Yes</td>
<td>No</td>
<td>Unused</td>
</tr>
<tr>
<td>1000</td>
<td><code class="language-plaintext highlighter-rouge">CLOSE_NORMAL</code></td>
<td>No</td>
<td>No</td>
<td>Successful operation / regular socket shutdown</td>
</tr>
<tr>
<td>1001</td>
<td><code class="language-plaintext highlighter-rouge">CLOSE_GOING_AWAY</code></td>
<td>No</td>
<td>No</td>
<td>Client is leaving (browser tab closing)</td>
</tr>
<tr>
<td>1002</td>
<td><code class="language-plaintext highlighter-rouge">CLOSE_PROTOCOL_ERROR</code></td>
<td>Yes</td>
<td>No</td>
<td>Endpoint received a malformed frame</td>
</tr>
<tr>
<td>1003</td>
<td><code class="language-plaintext highlighter-rouge">CLOSE_UNSUPPORTED</code></td>
<td>Yes</td>
<td>No</td>
<td>Endpoint received an unsupported frame (e.g. binary-only endpoint received text frame)</td>
</tr>
<tr>
<td>1004</td>
<td> </td>
<td>Yes</td>
<td>No</td>
<td>Reserved</td>
</tr>
<tr>
<td>1005</td>
<td><code class="language-plaintext highlighter-rouge">CLOSED_NO_STATUS</code></td>
<td>Yes</td>
<td>No</td>
<td>Expected close status, received none</td>
</tr>
<tr>
<td>1006</td>
<td><code class="language-plaintext highlighter-rouge">CLOSE_ABNORMAL</code></td>
<td>Yes</td>
<td>No</td>
<td>No close code frame has been receieved</td>
</tr>
<tr>
<td>1007</td>
<td><em>Unsupported payload</em></td>
<td>Yes</td>
<td>No</td>
<td>Endpoint received inconsistent message (e.g. malformed UTF-8)</td>
</tr>
<tr>
<td>1008</td>
<td><em>Policy violation</em></td>
<td>No</td>
<td>No</td>
<td>Generic code used for situations other than 1003 and 1009</td>
</tr>
<tr>
<td>1009</td>
<td><code class="language-plaintext highlighter-rouge">CLOSE_TOO_LARGE</code></td>
<td>No</td>
<td>No</td>
<td>Endpoint won’t process large frame</td>
</tr>
<tr>
<td>1010</td>
<td><em>Mandatory extension</em></td>
<td>No</td>
<td>No</td>
<td>Client wanted an extension which server did not negotiate</td>
</tr>
<tr>
<td>1011</td>
<td><em>Server error</em></td>
<td>No</td>
<td>No</td>
<td>Internal server error while operating</td>
</tr>
<tr>
<td>1012</td>
<td><em>Service restart</em></td>
<td>No</td>
<td>No</td>
<td>Server/service is restarting</td>
</tr>
<tr>
<td>1013</td>
<td><em>Try again later</em></td>
<td>No</td>
<td>No</td>
<td>Temporary server condition forced blocking client’s request</td>
</tr>
<tr>
<td>1014</td>
<td><em>Bad gateway</em></td>
<td>No</td>
<td>No</td>
<td>Server acting as gateway received an invalid response</td>
</tr>
<tr>
<td>1015</td>
<td><em>TLS handshake fail</em></td>
<td>Yes</td>
<td>No</td>
<td>Transport Layer Security handshake failure</td>
</tr>
<tr>
<td>1016 - 1999</td>
<td> </td>
<td>Yes</td>
<td>No</td>
<td>Reserved for later</td>
</tr>
<tr>
<td>2000 - 2999</td>
<td> </td>
<td>Yes</td>
<td>Yes</td>
<td>Reserved for websocket extensions</td>
</tr>
<tr>
<td>3000 - 3999</td>
<td> </td>
<td>No</td>
<td>Yes</td>
<td>Registered first come first serve at IANA</td>
</tr>
<tr>
<td>4000 - 4999</td>
<td> </td>
<td>No</td>
<td>Yes</td>
<td>Available for applications</td>
</tr>
</tbody>
</table>
Proxmox - Setting up user friendly urls2024-01-07T00:00:00+05:30id:/blog/2024/01/07/proxmox-user-friendly-urls<p>I have been recently tinkering up with idea of setting up a <a href="https://en.wikipedia.org/wiki/Home_server">homelab</a> and <a href="https://en.wikipedia.org/wiki/Self-hosting_(web_services)">self hosting</a> few services
such as <a href="https://en.wikipedia.org/wiki/Name_server">DNS Server</a>, <a href="https://en.wikipedia.org/wiki/Media_server">Media server</a>, <a href="https://en.wikipedia.org/wiki/News_aggregator">RSS Aggregator</a> etc along with some standard monitoring tools.</p>
<p>I evaluated two products, <a href="https://www.proxmox.com/en/">Proxmox</a> and <a href="https://unraid.net/">Unraid</a></p>
<p>After evaluating both, I choose Proxmox primarily because it seemed to fit my needs more than Unraid. Unraid is primarily useful if you have lot of data which needs to be made available to multiple services.</p>
<p>Whereas, Proxmox seems more suitable to run independent services altogether, either in [Docker](https://en.wikipedia.org/wiki/Docker_(software), <a href="https://en.wikipedia.org/wiki/Virtual_machine">VMs</a> or <a href="https://en.wikipedia.org/wiki/LXC">Linux Containers</a></p>
<p>For me, the size of the data was not going to be very much, atleast initially and I found the idea of running an open source software (Proxmox) more appealing than one I had to purchase (Unraid).</p>
<p>I had a spare 12 CORE / 32 GB machine on which I quickly setup Proxmox and I must say, I really like the way I was quickly able to run the following services in a day:</p>
<ul>
<li><a href="https://jellyfin.org/">Jellyfin</a> - Media server</li>
<li><a href="https://github.com/Athou/commafeed">Commafeed</a> - Open source RSS aggregator written in Java</li>
<li><a href="https://pi-hole.net/">PiHole</a> - DNS Server with Ad Blocking</li>
<li><a href="https://www.qbittorrent.org/">qbittorrent</a> - Torrent client</li>
<li><a href="https://nginxproxymanager.com/">Nginx Proxy Manager</a> - A simple reverse proxy</li>
<li><a href="https://prometheus.io/">Prometheus</a> - Well known time series database</li>
<li><a href="https://grafana.com/">Grafana</a> - Well known visualization platform</li>
</ul>
<p>The cool thing about <a href="https://www.proxmox.com/en/">Proxmox</a> is that it uses a <a href="https://www.ibm.com/topics/hypervisors">Type 1 Hypervisor</a> called <a href="https://ubuntu.com/blog/kvm-hyphervisor">Kernel-based Virtual Machine (KVM)</a> to run all the above mentioned services in <a href="https://wiki.debian.org/LXC">Linux Containers running on Debian Linux OS</a></p>
<p>What that essentially means is that all these services get their own unique IP Addresses on my LAN network as opposed to all running on a single IP Addresses and exposing on different ports. Type 1 Hypervisor means that they are able to directly access machine hardware and do not have the any additional layer or overhead of resource hungry VM’s.</p>
<p>Each LXC takes around 200 - 300 MB of disk space and around 512 MB of RAM , making them all fit in a single commodity machine.</p>
<p>One caveat of this approach is that now I have tons of IP addresses and ports for me to memorize to access these services when I require. Though I can bookmark them, I do not find it appealing.</p>
<p>Hence, I decided to setup a reverse proxy in nginx for each of them in order for me to reach them through a very user friendly url. Here is the screenshot of the setup:</p>
<p><img src="/images/nginx.png" height="600px" /></p>
<p>The above setup requires some manual setup of setting up a <a href="https://en.wikipedia.org/wiki/Hosts_(file)"><code class="language-plaintext highlighter-rouge">/etc/hosts</code></a> file with appropriate entries as follows</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Hosts for nginx proxy manager
192.168.1.133 nginx
192.168.1.133 plexui
192.168.1.133 tui
192.168.1.133 jellyfinui
192.168.1.133 grafanaui
192.168.1.133 promui
192.168.1.133 zkui
192.168.1.133 piholeui
192.168.1.133 rssui
</code></pre></div></div>
<p>Here <code class="language-plaintext highlighter-rouge">192.168.1.133</code> is the IP Address of <a href="https://github.com/NginxProxyManager/nginx-proxy-manager">Nginx Proxy Manager</a></p>
<p>With the above setup I can reach my services in friendly urls such as:</p>
<p>http://plexui, http://tui, http://jellyfinui, http://grafanaui etc.</p>
<h3 id="using-pihole-for-dns-records-instead-of-static-host-file">Using Pihole for DNS Records instead of static host file</h3>
<p>One might argue that these hosts entries need to be done in each individiaul client that needs to access Proxmox containers and is not a scaleable approach.</p>
<p>That is correct, the reason I went with hosts file approach is that right now, I only have a single client - my desktop PC and I do not foresee running PiHole 24x7.</p>
<p>Incase, I choose PiHole as my permanent DNS resolver and AdBlocker, the scaleable way would be to add these entries in PiHole itself so that it becomes available to any client within my network.</p>
Using Kitty hints to parse maven output2023-12-28T00:00:00+05:30id:/blog/2023/12/28/using-kitty-hints-parse-maven-output<p><a href="https://github.com/kovidgoyal/kitty">Kitty</a> is a very fast and feature rich terminal which has soon become my favourite.</p>
<p>One of the powerful features of Kitty is its customizability.</p>
<p>One such feature is <a href="https://sw.kovidgoyal.net/kitty/kittens/hints/">Kitty hints</a></p>
<p>Kitty hints allows you to select text on terminal and perform an action through a shortcut key. It is completely customizeable.</p>
<p>Common kitty hints action include opening files, folders and opening web urls outputted in terminal.</p>
<p>One example where, I was able to customize it was to parse maven output and makes the file links clickable to land directly on the line numbers.</p>
<p>For example, consider this build output:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.618 s
[INFO] Finished at: 2023-12-28T10:08:38+05:30
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.0:compile (default-compile) on project reactive-programming: Compilation failure: Compilation failure:
[ERROR] /run/media/madhur/centos/home/madhur/github/personal/reactive-programming/src/main/java/in/co/madhur/mono/FileService.java:[17,9] invalid method declaration; return type required
[ERROR] /run/media/madhur/centos/home/madhur/github/personal/reactive-programming/src/main/java/in/co/madhur/mono/FileService.java:[17,20] illegal start of type
[ERROR] /run/media/madhur/centos/home/madhur/github/personal/reactive-programming/src/main/java/in/co/madhur/mono/Lec08MonoRunnable.java:[15,54] ';' expected
[ERROR] /run/media/madhur/centos/home/madhur/github/personal/reactive-programming/src/main/java/in/co/madhur/mono/Lec06SupplierRefactoring.java:[25,37] ';' expected
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
</code></pre></div></div>
<p>Using kitty hints, we can make the file paths to <code class="language-plaintext highlighter-rouge">FileService.java</code> , <code class="language-plaintext highlighter-rouge">Lec08MonoRunnable.java</code> and <code class="language-plaintext highlighter-rouge">Lec06SupplierRefactoring.java</code> clickable and land it directly on appropriate line numbers.</p>
<p>This required writing custom regex to parse this line and emitting two named capturing groups <code class="language-plaintext highlighter-rouge">path</code> and <code class="language-plaintext highlighter-rouge">line</code>.</p>
<p>This can be done in <code class="language-plaintext highlighter-rouge">kitty.conf</code> as :</p>
<div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">map</span> <span class="n">kitty_mod</span>+<span class="n">p</span> <span class="n">kitten</span> <span class="n">hints</span> --<span class="n">type</span>=<span class="n">regex</span> --<span class="n">regex</span>=<span class="s2">"(?<path>(?:\/[\w-_^ ]+)+\/?(?:[\w.])+[^.]):\[(?<line>\d+),\d+\].?"</span> --<span class="n">program</span> <span class="s2">"launch /home/madhur/scripts/editor.py"</span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">editor.py</code> is a simple program which is invoked with parameters <code class="language-plaintext highlighter-rouge">path=...../FileService.java</code> and <code class="language-plaintext highlighter-rouge">line=17</code></p>
<p>In my case, I launch <a href="https://github.com/neovim/neovim"><code class="language-plaintext highlighter-rouge">Neovim</code></a> or <a href="https://www.jetbrains.com/idea/"><code class="language-plaintext highlighter-rouge">IntelliJ</code></a> depending upon if <code class="language-plaintext highlighter-rouge">Intellij</code> is previously running.</p>
<p>This can be simply done through following snippet:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/env python3
</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">subprocess</span>
<span class="n">myObject</span> <span class="o">=</span> <span class="p">{}</span>
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">:</span>
<span class="k">if</span> <span class="s">'='</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">line</span><span class="p">:</span>
<span class="k">continue</span>
<span class="k">print</span><span class="p">(</span><span class="n">line</span><span class="p">)</span>
<span class="n">key</span><span class="p">,</span> <span class="n">value</span> <span class="o">=</span> <span class="n">line</span><span class="p">.</span><span class="n">rstrip</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">).</span><span class="n">split</span><span class="p">(</span><span class="s">"="</span><span class="p">)</span>
<span class="n">myObject</span><span class="p">[</span><span class="n">key</span><span class="p">]</span> <span class="o">=</span> <span class="n">value</span>
<span class="k">print</span><span class="p">(</span><span class="n">myObject</span><span class="p">)</span>
<span class="n">idea_running</span> <span class="o">=</span> <span class="bp">False</span>
<span class="n">pl</span> <span class="o">=</span> <span class="n">subprocess</span><span class="p">.</span><span class="n">Popen</span><span class="p">([</span><span class="s">'ps'</span><span class="p">,</span> <span class="s">'aux'</span><span class="p">],</span> <span class="n">stdout</span><span class="o">=</span><span class="n">subprocess</span><span class="p">.</span><span class="n">PIPE</span><span class="p">).</span><span class="n">communicate</span><span class="p">()[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">list_ps</span> <span class="o">=</span> <span class="n">pl</span><span class="p">.</span><span class="n">decode</span><span class="p">(</span><span class="s">'utf-8'</span><span class="p">)</span>
<span class="k">if</span> <span class="s">'intellij'</span> <span class="ow">in</span> <span class="n">list_ps</span><span class="p">:</span>
<span class="n">idea_running</span> <span class="o">=</span> <span class="bp">True</span>
<span class="k">if</span> <span class="n">idea_running</span> <span class="ow">is</span> <span class="bp">True</span> <span class="ow">and</span> <span class="s">'java'</span> <span class="ow">in</span> <span class="n">myObject</span><span class="p">[</span><span class="s">'path'</span><span class="p">]:</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Intellij found"</span><span class="p">)</span>
<span class="n">os</span><span class="p">.</span><span class="n">system</span><span class="p">(</span><span class="s">"flatpak run com.jetbrains.IntelliJ-IDEA-Community "</span> <span class="o">+</span> <span class="n">myObject</span><span class="p">[</span><span class="s">'path'</span><span class="p">])</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Intellij not found"</span><span class="p">)</span>
<span class="n">os</span><span class="p">.</span><span class="n">system</span><span class="p">(</span><span class="s">"nvim "</span> <span class="o">+</span> <span class="s">" +"</span><span class="o">+</span><span class="n">myObject</span><span class="p">[</span><span class="s">'line'</span><span class="p">]</span> <span class="o">+</span> <span class="s">" "</span> <span class="o">+</span> <span class="n">myObject</span><span class="p">[</span><span class="s">'path'</span><span class="p">])</span>
</code></pre></div></div>
Android SSL Pinning Bypass2023-12-05T00:00:00+05:30id:/blog/2023/12/05/android-ssl-pinning-bypass<p>I have always been a tinkerer and loved to explore internal of applications.</p>
<p>Android has always fascinated me from the beginning partly because its mostly open source and allows you to customize. Other aspect of Android which has been useful is <a href="https://en.wikipedia.org/wiki/Rooting_(Android)">Rooting</a></p>
<p>Rooting allows you to take complete control over the application and customize some of the aspects which are not possible in non-rooted Android.</p>
<p>One area where rooting helps is capturing SSL network traffic of the applications. This helps in debugging and learning more about how networks works. In this post, we will see how can we capture the network traffic for inspection in Android. This is mostly for debugging and learning purpose.</p>
<h3 id="apk-mitm"><a href="https://github.com/shroudedcode/apk-mitm">Apk-mitm</a></h3>
<hr />
<p>APK mitm tools modified the original Android application to disable the SSL Pinning. This tool relies on <a href="https://apktool.org/">apktool</a> heavily.</p>
<p>This tool always doesn’t work. Recently there has been lot of <a href="https://github.com/shroudedcode/apk-mitm/issues/141">issues</a> which have no solutions.</p>
<p>However, if it works, this is the most easiest and straightforward way as the modified app can be installed on non-rooted device and just inspected through proxy such as <a href="https://www.charlesproxy.com/">Charles</a></p>
<h3 id="android-unpinner"><a href="https://github.com/mitmproxy/android-unpinner">Android Unpinner</a></h3>
<hr />
<p>This tool is different from Apk Mitm in a way that it bypasses the SSL pinning code only at runtime. Thus, you no longer have to disassemble and repackage the application.</p>
<p>This tool relies heavily on <a href="https://github.com/frida/frida-tools">Frida tools</a> for its job.</p>
<p>Again, this tool is not gauranteed to work 100% of the time.</p>
<h3 id="rooting"><a href="https://en.wikipedia.org/wiki/Rooting_(Android)">Rooting</a></h3>
<hr />
<p>If you can root your device, it is very easy to capture network traffic even if its from SSL pinned app. However, rooting can destroy warranty.</p>
<p>These days, most of the Android apps, will refuse to start if they detect that they are running on a rooted software.</p>
<p>Hence, this option is only viable if you have a spare device.</p>
<h3 id="rooted-avd"><a href="https://gitlab.com/newbit/rootAVD">Rooted AVD</a></h3>
<hr />
<p>This is my most preferred method and mostly always works. Since the purpose of network inspection is mostly debugging and learning, we can do it on AVD instead of actual device.</p>
<p><a href="https://gitlab.com/newbit/rootAVD">rootAVD</a> allows you to seemlessly root the running AVD device, using the simple command</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./rootAVD.sh system-images/android-33/google_apis_playstore/x86_64/ramdisk.img
</code></pre></div></div>
<p>where <code class="language-plaintext highlighter-rouge">system-images/android-33/google_apis_playstore/x86_64/ramdisk.img</code> is the path of AVD devices relative to <code class="language-plaintext highlighter-rouge">$ANDROID_HOME</code></p>
<p>Next step, would be to install <a href="https://github.com/NVISOsecurity/MagiskTrustUserCerts">MagiskTrustUserCerts</a> module of <a href="https://github.com/topjohnwu/Magisk">Magisk</a></p>
<blockquote>
<p>This module makes all installed user certificates part of the system certificate store, so that they will
automatically be used when building the trust chain. This module makes it unnecessary to add the
network_security_config property to an application’s manifest.</p>
</blockquote>
<p>Once installed, your Magisk modules section should look like this</p>
<p><img src="/images/magisk.png" height="600px" /></p>
<p>Now, the only step remaining is enabling proxy to intercept traffic. I use <a href="https://www.charlesproxy.com/">Charles proxy</a> and this opens the port 8888 for listening to traffic.</p>
<p>Enabling proxy on AVD is as simple as executing:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb shell settings put global http_proxy 192.168.1.252:8888
</code></pre></div></div>
<p>where <code class="language-plaintext highlighter-rouge">192.168.1.252</code> is the local IP address of the host machine.</p>
<p>Disabling proxy can be done using:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb shell settings put global http_proxy :0
</code></pre></div></div>
<hr />
<p>If you guys have been using more preferred way to inspect SSL traffic. Let me know in the comments.</p>